mirror of
https://github.com/gravitational/teleport
synced 2024-10-20 17:23:22 +00:00
Respect [HTTP(S)|NO]_PROXY
envs when dialing directly to Kube via SPDY (#30624)
* Respect `[HTTP(S)|NO]_PROXY` envs when dialing directly to Kube via SPDY This PR enables support for proxy env's when dialing directly to the Kubernetes Cluster - `kubernetes_service` and `legacy_proxy` when the cluster is local - for the SPDY protocol used by `kubectl exec` and `kubectl portforward`. PR #30583 introduced support for normal HTTP requests but missed support for SPDY requests. Fixes #30550 Signed-off-by: Tiago Silva <tiago.silva@goteleport.com> * add proxier helper --------- Signed-off-by: Tiago Silva <tiago.silva@goteleport.com>
This commit is contained in:
parent
c1050a265c
commit
3dfaa8f1c1
|
@ -28,6 +28,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -50,6 +51,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
kubeexec "k8s.io/client-go/util/exec"
|
||||
|
@ -2070,6 +2072,7 @@ func (f *Forwarder) getExecutor(ctx authContext, sess *clusterSession, req *http
|
|||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
upgradeRoundTripper := NewSpdyRoundTripperWithDialer(roundTripperConfig{
|
||||
ctx: req.Context(),
|
||||
authCtx: ctx,
|
||||
|
@ -2078,6 +2081,7 @@ func (f *Forwarder) getExecutor(ctx authContext, sess *clusterSession, req *http
|
|||
pingPeriod: f.cfg.ConnPingPeriod,
|
||||
originalHeaders: req.Header,
|
||||
useIdentityForwarding: useImpersonation,
|
||||
proxier: sess.getProxier(),
|
||||
})
|
||||
rt := http.RoundTripper(upgradeRoundTripper)
|
||||
if sess.kubeAPICreds != nil {
|
||||
|
@ -2101,6 +2105,7 @@ func (f *Forwarder) getSPDYDialer(ctx authContext, sess *clusterSession, req *ht
|
|||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
upgradeRoundTripper := NewSpdyRoundTripperWithDialer(roundTripperConfig{
|
||||
ctx: req.Context(),
|
||||
authCtx: ctx,
|
||||
|
@ -2109,6 +2114,7 @@ func (f *Forwarder) getSPDYDialer(ctx authContext, sess *clusterSession, req *ht
|
|||
pingPeriod: f.cfg.ConnPingPeriod,
|
||||
originalHeaders: req.Header,
|
||||
useIdentityForwarding: useImpersonation,
|
||||
proxier: sess.getProxier(),
|
||||
})
|
||||
rt := http.RoundTripper(upgradeRoundTripper)
|
||||
if sess.kubeAPICreds != nil {
|
||||
|
@ -2205,23 +2211,37 @@ func (s *clusterSession) monitorConn(conn net.Conn, err error) (net.Conn, error)
|
|||
}
|
||||
|
||||
func (s *clusterSession) Dial(network, addr string) (net.Conn, error) {
|
||||
return s.monitorConn(s.dial(s.requestContext, network))
|
||||
return s.monitorConn(s.dial(s.requestContext, network, addr))
|
||||
}
|
||||
|
||||
func (s *clusterSession) DialWithContext(opts ...contextDialerOption) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return s.monitorConn(s.dial(ctx, network, opts...))
|
||||
return s.monitorConn(s.dial(ctx, network, addr, opts...))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *clusterSession) dial(ctx context.Context, network string, opts ...contextDialerOption) (net.Conn, error) {
|
||||
func (s *clusterSession) dial(ctx context.Context, network, addr string, opts ...contextDialerOption) (net.Conn, error) {
|
||||
dialer := s.parent.getContextDialerFunc(s, opts...)
|
||||
|
||||
conn, err := dialer(ctx, network, s.targetAddr)
|
||||
conn, err := dialer(ctx, network, addr)
|
||||
|
||||
return conn, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// getProxier returns the proxier function to use for this session.
|
||||
// If the target cluster is not served by this teleport service, the proxier
|
||||
// must be nil to avoid using it through the reverse tunnel.
|
||||
// If the target cluster is served by this teleport service, the proxier
|
||||
// must be set to the default proxy function.
|
||||
func (s *clusterSession) getProxier() func(req *http.Request) (*url.URL, error) {
|
||||
// When the target cluster is not served by this teleport service, the
|
||||
// proxier must be nil to avoid using it through the reverse tunnel.
|
||||
if s.kubeAPICreds == nil {
|
||||
return nil
|
||||
}
|
||||
return utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
|
||||
}
|
||||
|
||||
// TODO(awly): unit test this
|
||||
func (f *Forwarder) newClusterSession(ctx context.Context, authCtx authContext) (*clusterSession, error) {
|
||||
ctx, span := f.cfg.tracer.Start(
|
||||
|
|
|
@ -40,8 +40,8 @@ import (
|
|||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/third_party/forked/golang/netutil"
|
||||
|
||||
apiclient "github.com/gravitational/teleport/api/client"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports
|
||||
|
@ -88,6 +88,8 @@ type roundTripperConfig struct {
|
|||
// auth.TeleportImpersonateUserHeader and auth.TeleportImpersonateIPHeader
|
||||
// headers instead of relying on the certificate to transport it.
|
||||
useIdentityForwarding bool
|
||||
|
||||
proxier func(*http.Request) (*url.URL, error)
|
||||
}
|
||||
|
||||
// NewSpdyRoundTripperWithDialer creates a new SpdyRoundTripper that will use
|
||||
|
@ -104,7 +106,7 @@ func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config {
|
|||
|
||||
// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer.
|
||||
func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
|
||||
conn, err := s.dial(req.URL)
|
||||
conn, err := s.dial(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -118,37 +120,80 @@ func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
|
|||
}
|
||||
|
||||
// dial dials the host specified by url, using TLS if appropriate.
|
||||
func (s *SpdyRoundTripper) dial(url *url.URL) (net.Conn, error) {
|
||||
dialAddr := netutil.CanonicalAddr(url)
|
||||
|
||||
if url.Scheme == "http" {
|
||||
switch {
|
||||
case s.dialWithContext != nil:
|
||||
return s.dialWithContext(s.ctx, "tcp", dialAddr)
|
||||
default:
|
||||
return net.Dial("tcp", dialAddr)
|
||||
func (s *SpdyRoundTripper) dial(req *http.Request) (conn net.Conn, err error) {
|
||||
var proxyURL *url.URL
|
||||
if s.proxier != nil {
|
||||
proxyURL, err = s.proxier(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO validate the TLSClientConfig is set up?
|
||||
var conn *tls.Conn
|
||||
var err error
|
||||
if s.dialWithContext == nil {
|
||||
conn, err = tls.Dial("tcp", dialAddr, s.tlsConfig)
|
||||
if proxyURL == nil {
|
||||
conn, err = s.dialWithoutProxy(req.URL)
|
||||
} else {
|
||||
conn, err = utils.TLSDial(s.ctx, utils.DialWithContextFunc(s.dialWithContext), "tcp", dialAddr, s.tlsConfig)
|
||||
conn, err = s.dialWithProxy(req, proxyURL)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if req.URL.Scheme == "https" {
|
||||
return s.tlsConn(s.ctx, conn, netutil.CanonicalAddr(req.URL))
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *SpdyRoundTripper) dialWithoutProxy(url *url.URL) (conn net.Conn, err error) {
|
||||
dialAddr := netutil.CanonicalAddr(url)
|
||||
switch {
|
||||
case s.dialWithContext != nil:
|
||||
conn, err = s.dialWithContext(s.ctx, "tcp", dialAddr)
|
||||
default:
|
||||
conn, err = net.Dial("tcp", dialAddr)
|
||||
}
|
||||
return conn, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// tlsConn returns a TLS client side connection using rwc as the underlying transport.
|
||||
func (s *SpdyRoundTripper) tlsConn(ctx context.Context, rwc net.Conn, targetHost string) (net.Conn, error) {
|
||||
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)
|
||||
|
||||
// Client handshake will verify the server hostname and cert chain. That
|
||||
// way we can err our before first read/write.
|
||||
if err := conn.Handshake(); err != nil {
|
||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||
tlsConn.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
// dialWithProxy dials the host specified by url through an http or an socks5 proxy.
|
||||
func (s *SpdyRoundTripper) dialWithProxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) {
|
||||
// ensure we use a canonical host with proxyReq
|
||||
targetHost := netutil.CanonicalAddr(req.URL)
|
||||
|
||||
proxyDialConn, err := apiclient.DialProxyWithDialer(
|
||||
s.ctx,
|
||||
proxyURL,
|
||||
targetHost,
|
||||
apiclient.ContextDialerFunc(s.dialWithContext),
|
||||
)
|
||||
return proxyDialConn, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// RoundTrip executes the Request and upgrades it. After a successful upgrade,
|
||||
|
@ -193,7 +238,6 @@ func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
|
|||
conn,
|
||||
),
|
||||
)
|
||||
|
||||
resp, err := http.ReadResponse(responseReader, nil)
|
||||
if err != nil {
|
||||
if conn != nil {
|
||||
|
|
Loading…
Reference in a new issue