Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v9] Don't respect HTTP_PROXY env in k8 forwarder (#11257) #11462

Merged
merged 4 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions lib/kube/proxy/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
"time"

"github.com/google/uuid"
"golang.org/x/net/http2"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
Expand Down Expand Up @@ -64,7 +66,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/httpstream"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/client-go/transport/spdy"
)
Expand Down Expand Up @@ -1515,7 +1516,11 @@ func (f *Forwarder) makeSessionForwarder(sess *clusterSession) (*forward.Forward
transport := f.newTransport(sess.Dial, sess.tlsConfig)

if sess.upgradeToHTTP2 {
transport = utilnet.SetTransportDefaults(transport)
// Upgrade transport to h2 where HTTP_PROXY and HTTPS_PROXY
// envs are not take into account purposely.
if err := http2.ConfigureTransport(transport); err != nil {
return nil, trace.Wrap(err)
}
} else {
sess.tlsConfig.NextProtos = nil
}
Expand Down
108 changes: 108 additions & 0 deletions lib/kube/proxy/forwarder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"sort"
"sync/atomic"
"testing"
"time"

Expand Down Expand Up @@ -796,6 +799,80 @@ func TestClusterSessionDial(t *testing.T) {
require.Equal(t, "addr1", sess.kubeAddress)
}

// TestKubeFwdHTTPProxyEnv ensures that kube forwarder doesn't respect HTTPS_PROXY env
// and Kubernetes API is called directly.
func TestKubeFwdHTTPProxyEnv(t *testing.T) {
ctx := context.Background()
f := newMockForwader(ctx, t)
authCtx := mockAuthCtx(ctx, t, "kube-cluster", false)

lockWatcher, err := services.NewLockWatcher(ctx, services.LockWatcherConfig{
ResourceWatcherConfig: services.ResourceWatcherConfig{
Component: teleport.ComponentNode,
Client: &mockEventClient{},
},
})
require.NoError(t, err)
f.cfg.LockWatcher = lockWatcher

var kubeAPICallCount uint32
mockKubeAPI := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddUint32(&kubeAPICallCount, 1)
}))

authCtx.teleportCluster.dial = func(ctx context.Context, network, addr, _ string) (net.Conn, error) {
return new(net.Dialer).DialContext(ctx, mockKubeAPI.Listener.Addr().Network(), mockKubeAPI.Listener.Addr().String())
}

checkTransportProxy := func(rt http.RoundTripper) http.RoundTripper {
tr, ok := rt.(*http.Transport)
require.True(t, ok)
require.Nil(t, tr.Proxy, "kube forwarder should not take into account HTTPS_PROXY env")
return rt
}

f.creds = map[string]*kubeCreds{
"local": {
targetAddr: mockKubeAPI.URL,
tlsConfig: mockKubeAPI.TLS,
transportConfig: &transport.Config{
WrapTransport: checkTransportProxy,
},
},
}

authCtx.kubeCluster = "local"
sess, err := f.newClusterSession(authCtx)
require.NoError(t, err)
require.Equal(t, []kubeClusterEndpoint{{addr: f.creds["local"].targetAddr}}, sess.kubeClusterEndpoints)

sess.tlsConfig.InsecureSkipVerify = true

t.Setenv("HTTP_PROXY", "example.com:9999")
t.Setenv("HTTPS_PROXY", "example.com:9999")

// Set upgradeToHTTP2 to trigger h2 transport upgrade logic.
sess.upgradeToHTTP2 = true
fwd, err := f.makeSessionForwarder(sess)
require.NoError(t, err)

// Create KubeProxy that uses fwd and forward incoming request to kubernetes API.
kubeProxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.URL, err = url.Parse(mockKubeAPI.URL)
require.NoError(t, err)
fwd.ServeHTTP(w, r)
}))

req, err := http.NewRequest("GET", kubeProxy.URL, nil)
require.NoError(t, err)

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Equal(t, uint32(1), atomic.LoadUint32(&kubeAPICallCount))
require.NoError(t, resp.Body.Close())
}

func newMockForwader(ctx context.Context, t *testing.T) *Forwarder {
clientCreds, err := ttlmap.New(defaults.ClientCacheSize)
require.NoError(t, err)
Expand Down Expand Up @@ -944,3 +1021,34 @@ type mockAuthorizer struct {
func (a mockAuthorizer) Authorize(context.Context) (*auth.Context, error) {
return a.ctx, a.err
}

type mockEventClient struct {
services.Presence
types.Events
services.LockGetter
}

func (c *mockEventClient) NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) {
return &mockWatcher{
ctx: ctx,
eventC: make(chan types.Event),
}, nil
}

type mockWatcher struct {
ctx context.Context
types.Watcher
eventC chan types.Event
}

func (m *mockWatcher) Close() error {
return nil
}

func (m *mockWatcher) Events() <-chan types.Event {
return m.eventC
}

func (m *mockWatcher) Done() <-chan struct{} {
return m.ctx.Done()
}