sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/proxy/dial.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 proxy 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "net/http" 24 "time" 25 26 "github.com/pkg/errors" 27 corev1 "k8s.io/api/core/v1" 28 kerrors "k8s.io/apimachinery/pkg/util/errors" 29 "k8s.io/client-go/kubernetes" 30 "k8s.io/client-go/tools/portforward" 31 "k8s.io/client-go/transport/spdy" 32 ) 33 34 const defaultTimeout = 10 * time.Second 35 36 // Dialer creates connections using Kubernetes API Server port-forwarding. 37 type Dialer struct { 38 proxy Proxy 39 clientset *kubernetes.Clientset 40 proxyTransport http.RoundTripper 41 upgrader spdy.Upgrader 42 timeout time.Duration 43 } 44 45 // NewDialer creates a new dialer for a given API server scope. 46 func NewDialer(p Proxy, options ...func(*Dialer) error) (*Dialer, error) { 47 if p.Port == 0 { 48 return nil, errors.New("port required") 49 } 50 51 dialer := &Dialer{ 52 proxy: p, 53 } 54 55 for _, option := range options { 56 err := option(dialer) 57 if err != nil { 58 return nil, err 59 } 60 } 61 62 if dialer.timeout == 0 { 63 dialer.timeout = defaultTimeout 64 } 65 p.KubeConfig.Timeout = dialer.timeout 66 clientset, err := kubernetes.NewForConfig(p.KubeConfig) 67 if err != nil { 68 return nil, err 69 } 70 proxyTransport, upgrader, err := spdy.RoundTripperFor(p.KubeConfig) 71 if err != nil { 72 return nil, err 73 } 74 dialer.proxyTransport = proxyTransport 75 dialer.upgrader = upgrader 76 dialer.clientset = clientset 77 return dialer, nil 78 } 79 80 // DialContextWithAddr is a GO grpc compliant dialer construct. 81 func (d *Dialer) DialContextWithAddr(ctx context.Context, addr string) (net.Conn, error) { 82 return d.DialContext(ctx, scheme, addr) 83 } 84 85 // DialContext creates proxied port-forwarded connections. 86 // ctx is currently unused, but fulfils the type signature used by GRPC. 87 func (d *Dialer) DialContext(_ context.Context, _ string, addr string) (net.Conn, error) { 88 req := d.clientset.CoreV1().RESTClient(). 89 Post(). 90 Resource(d.proxy.Kind). 91 Namespace(d.proxy.Namespace). 92 Name(addr). 93 SubResource("portforward") 94 95 dialer := spdy.NewDialer(d.upgrader, &http.Client{Transport: d.proxyTransport}, "POST", req.URL()) 96 97 // Create a new connection from the dialer. 98 // 99 // Warning: Any early return should close this connection, otherwise we're going to leak them. 100 connection, _, err := dialer.Dial(portforward.PortForwardProtocolV1Name) 101 if err != nil { 102 return nil, errors.Wrap(err, "error upgrading connection") 103 } 104 105 // Create the headers. 106 headers := http.Header{} 107 108 // Set the header port number to match the proxy one. 109 headers.Set(corev1.PortHeader, fmt.Sprintf("%d", d.proxy.Port)) 110 111 // We only create a single stream over the connection 112 headers.Set(corev1.PortForwardRequestIDHeader, "0") 113 114 // Create the error stream. 115 headers.Set(corev1.StreamType, corev1.StreamTypeError) 116 errorStream, err := connection.CreateStream(headers) 117 if err != nil { 118 return nil, kerrors.NewAggregate([]error{ 119 err, 120 connection.Close(), 121 }) 122 } 123 // Close the error stream right away, we're not writing to it. 124 if err := errorStream.Close(); err != nil { 125 return nil, kerrors.NewAggregate([]error{ 126 err, 127 connection.Close(), 128 }) 129 } 130 131 // Create the data stream. 132 // 133 // NOTE: Given that we're reusing the headers, 134 // we need to overwrite the stream type before creating it. 135 headers.Set(corev1.StreamType, corev1.StreamTypeData) 136 dataStream, err := connection.CreateStream(headers) 137 if err != nil { 138 return nil, kerrors.NewAggregate([]error{ 139 errors.Wrap(err, "error creating forwarding stream"), 140 connection.Close(), 141 }) 142 } 143 144 // Create the net.Conn and return. 145 return NewConn(connection, dataStream), nil 146 } 147 148 // DialTimeout sets the timeout. 149 func DialTimeout(duration time.Duration) func(*Dialer) error { 150 return func(d *Dialer) error { 151 return d.setTimeout(duration) 152 } 153 } 154 155 func (d *Dialer) setTimeout(duration time.Duration) error { 156 d.timeout = duration 157 return nil 158 }