Pull kube proxy address from proxy ping endpoint (#23821)

This PR picks the kubernetes proxy address from `webapi/ping` endpoint
when tls routing is disabled and the user didn't provided the `--proxy`
flag when calling `tctl auth sign --format=kubernetes`

If tls routing is enabled, it takes precendence over `kube_public_addr`.

Fixes #10396
This commit is contained in:
Tiago Silva 2023-04-13 10:07:19 +01:00 committed by GitHub
parent 88fb60c164
commit f62d170226
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 8 deletions

View file

@ -18,11 +18,9 @@ import (
"context"
"fmt"
"io"
"net"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"text/template"
"time"
@ -33,6 +31,7 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/client/webclient"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
@ -89,12 +88,14 @@ type AuthCommand struct {
authRotate *kingpin.CmdClause
authLS *kingpin.CmdClause
authCRL *kingpin.CmdClause
// testInsecureSkipVerify is used to skip TLS verification during tests
// when connecting to the proxy ping address.
testInsecureSkipVerify bool
}
// Initialize allows TokenCommand to plug itself into the CLI parser
func (a *AuthCommand) Initialize(app *kingpin.Application, config *servicecfg.Config) {
a.config = config
// operations with authorities
auth := app.Command("auth", "Operations with user and host certificate authorities (CAs)").Hidden()
a.authExport = auth.Command("export", "Export public cluster (CA) keys to stdout")
@ -956,7 +957,7 @@ func (a *AuthCommand) checkProxyAddr(ctx context.Context, clusterAPI auth.Client
if addr == "" {
continue
}
// if the proxy is multiplexing, the public address is the web proxy address.
if netConfig.GetProxyListenerMode() == types.ProxyListenerMode_Multiplex {
u := url.URL{
Scheme: "https",
@ -966,14 +967,32 @@ func (a *AuthCommand) checkProxyAddr(ctx context.Context, clusterAPI auth.Client
return nil
}
uaddr, err := utils.ParseAddr(addr)
_, err := utils.ParseAddr(addr)
if err != nil {
log.Warningf("Invalid public address on the proxy %q: %q: %v.", p.GetName(), addr, err)
continue
}
ping, err := webclient.Ping(
&webclient.Config{
Context: ctx,
ProxyAddr: addr,
Timeout: 5 * time.Second,
Insecure: a.testInsecureSkipVerify,
},
)
if err != nil {
log.Warningf("Unable to ping proxy public address on the proxy %q: %q: %v.", p.GetName(), addr, err)
continue
}
if !ping.Proxy.Kube.Enabled || ping.Proxy.Kube.PublicAddr == "" {
continue
}
u := url.URL{
Scheme: "https",
Host: net.JoinHostPort(uaddr.Host(), strconv.Itoa(defaults.KubeListenPort)),
Host: ping.Proxy.Kube.PublicAddr,
}
a.proxyAddr = u.String()
return nil

View file

@ -18,6 +18,10 @@ import (
"bytes"
"context"
"crypto/x509/pkix"
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
@ -27,6 +31,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/client"
@ -40,7 +45,11 @@ import (
)
func TestAuthSignKubeconfig(t *testing.T) {
t.Parallel()
// create a HTTPS_PROXY endpoint that intercepts the proxy Ping request
// and returns a mock response
// We need to do this because the Ping request is made using a custom
// http.Transport and we can't use a custom dialer to intercept the request.
t.Setenv("HTTPS_PROXY", newHTTPSProxy(t))
tmpDir := t.TempDir()
@ -201,8 +210,9 @@ func TestAuthSignKubeconfig(t *testing.T) {
Enabled: false,
},
}},
testInsecureSkipVerify: true,
},
wantAddr: "https://proxy-from-api.example.com:3026",
wantAddr: "https://proxy-from-api.example.com:3060",
assertErr: require.NoError,
},
{
@ -218,6 +228,7 @@ func TestAuthSignKubeconfig(t *testing.T) {
Enabled: false,
},
}},
testInsecureSkipVerify: true,
},
wantCluster: remoteCluster.GetMetadata().Name,
assertErr: require.NoError,
@ -235,6 +246,7 @@ func TestAuthSignKubeconfig(t *testing.T) {
Enabled: false,
},
}},
testInsecureSkipVerify: true,
},
assertErr: func(t require.TestingT, err error, _ ...interface{}) {
require.Error(t, err)
@ -276,6 +288,7 @@ func TestAuthSignKubeconfig(t *testing.T) {
Enabled: false,
},
}},
testInsecureSkipVerify: true,
},
wantAddr: "https://proxy-from-api.example.com:3080",
assertErr: require.NoError,
@ -316,6 +329,63 @@ func TestAuthSignKubeconfig(t *testing.T) {
}
}
// proxyHandler is a simple HTTP handler that proxies all requests to the
// upstreamAddr.
type proxyHandler struct {
upstreamAddr string
}
func (p *proxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
dest_conn, err := net.DialTimeout("tcp", p.upstreamAddr, 10*time.Second)
if err != nil {
http.Error(wr, err.Error(), http.StatusServiceUnavailable)
return
}
wr.WriteHeader(http.StatusOK)
hijacker, ok := wr.(http.Hijacker)
if !ok {
http.Error(wr, "Hijacking not supported", http.StatusInternalServerError)
return
}
client_conn, _, err := hijacker.Hijack()
if err != nil {
http.Error(wr, err.Error(), http.StatusServiceUnavailable)
}
utils.ProxyConn(req.Context(), client_conn, dest_conn)
}
// pingSrv is a simple HTTP handler that returns a PingResponse with a
// kube proxy enabled.
type pingSrv struct{}
func (p *pingSrv) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
wr.WriteHeader(http.StatusOK)
json.NewEncoder(wr).Encode(
webclient.PingResponse{
Proxy: webclient.ProxySettings{
Kube: webclient.KubeProxySettings{
Enabled: true,
PublicAddr: "proxy-from-api.example.com:3060",
},
},
},
)
}
func newHTTPSProxy(t *testing.T) string {
pingTestServer := httptest.NewTLSServer(&pingSrv{})
t.Cleanup(func() { pingTestServer.Close() })
proxyTestServer := httptest.NewServer(&proxyHandler{
upstreamAddr: pingTestServer.Listener.Addr().String(),
})
t.Cleanup(func() { proxyTestServer.Close() })
return proxyTestServer.URL
}
type mockClient struct {
auth.ClientI