mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 09:44:51 +00:00
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:
parent
88fb60c164
commit
f62d170226
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue