kube: URL-based local-proxy routing for tsh proxy kube (#66099)

* kube: URL-based local-proxy routing

* kube: drop wildcard local CA and SNI prefix

* kube: unexport kubeClusterKey

* kube: add tests for local-proxy path helpers

* kube: write error msg for old URL format

* kube: support both formats to preserve compatibility

* kube: drop misleading 'regenerate kubeconfig' error

* kube: drop in-place request mutation

* kube: fix tests for URL-based routing

* kube: drop legacy SNI fallback in local proxy
This commit is contained in:
Jake Alti
2026-05-11 14:54:24 +00:00
committed by GitHub
parent 7c8318134c
commit 900f2957a8
10 changed files with 228 additions and 98 deletions
+2 -2
View File
@@ -780,10 +780,10 @@ func kubeClientForLocalProxy(t *testing.T, kubeconfigPath, teleportCluster, kube
CAData: config.Clusters[contextName].CertificateAuthorityData,
CertData: config.AuthInfos[contextName].ClientCertificateData,
KeyData: config.AuthInfos[contextName].ClientKeyData,
ServerName: alpncommon.KubeLocalProxySNI(teleportCluster, kubeCluster),
ServerName: teleportCluster,
}
client, err := kubernetes.NewForConfig(&rest.Config{
Host: "https://" + teleportCluster,
Host: "https://" + teleportCluster + alpncommon.KubeLocalProxyPathPrefix(teleportCluster, kubeCluster),
TLSClientConfig: tlsClientConfig,
Proxy: http.ProxyURL(proxyURL),
})
+9 -5
View File
@@ -390,7 +390,11 @@ func TestALPNSNIProxyKube(t *testing.T) {
// Teleport Proxy with a L7 LB in front.
t.Run("ALPN connection upgrade", func(t *testing.T) {
teleportCluster := suite.root.Config.Auth.ClusterName.GetClusterName()
kubeCluster := "gke_project_europecentral2a_cluster1"
// Must match the KubeCluster claim on the cert issued at the top of
// the test (see kube.ProxyConfig above) — URL-based routing on the
// upstream Teleport proxy verifies the cluster identifier in the
// path matches the cert claim.
kubeCluster := "root.example.com"
k8sClient := createALPNLocalKubeClient(t,
suite.root.Config.Proxy.WebAddr,
@@ -635,7 +639,7 @@ func TestKubePROXYProtocol(t *testing.T) {
k8Client = createALPNLocalKubeClient(t,
targetAddr,
testCluster.Secrets.SiteName,
kubeCluster,
kubeClusterName,
kubeConfig)
}
@@ -660,7 +664,7 @@ func createALPNLocalKubeClient(t *testing.T, targetAddr utils.NetAddr, teleportC
// Generate a self-signed CA for kube local proxy.
localCAKey, localCACert, err := tlsca.GenerateSelfSignedCA(pkix.Name{
CommonName: "localhost",
}, []string{alpncommon.KubeLocalProxyWildcardDomain(teleportCluster)}, defaults.CATTL)
}, []string{teleportCluster}, defaults.CATTL)
require.NoError(t, err)
// Make a mock ALB which points to the Teleport Proxy Service. Then
@@ -683,13 +687,13 @@ func createALPNLocalKubeClient(t *testing.T, targetAddr utils.NetAddr, teleportC
fp := mustStartKubeForwardProxy(t, lp.GetAddr())
k8Client, err := kubernetes.NewForConfig(&rest.Config{
Host: "https://" + teleportCluster,
Host: "https://" + teleportCluster + alpncommon.KubeLocalProxyPathPrefix(teleportCluster, kubeCluster),
Proxy: http.ProxyURL(mustParseURL(t, "http://"+fp.GetAddr())),
TLSClientConfig: rest.TLSClientConfig{
CAData: localCACert,
CertData: localCACert, // Client uses same cert as local proxy server.
KeyData: localCAKey,
ServerName: alpncommon.KubeLocalProxySNI(teleportCluster, kubeCluster),
ServerName: teleportCluster,
},
})
require.NoError(t, err)
+2 -2
View File
@@ -119,9 +119,9 @@ func CreateLocalProxyConfig(originalKubeConfig *clientcmdapi.Config, localProxyV
config.Clusters[contextName] = &clientcmdapi.Cluster{
ProxyURL: localProxyValues.LocalProxyURL,
Server: localProxyValues.TeleportKubeClusterAddr,
Server: localProxyValues.TeleportKubeClusterAddr + common.KubeLocalProxyPathPrefix(cluster.TeleportCluster, cluster.KubeCluster),
CertificateAuthorityData: localProxyValues.LocalProxyCAs[cluster.TeleportCluster],
TLSServerName: common.KubeLocalProxySNI(cluster.TeleportCluster, cluster.KubeCluster),
TLSServerName: cluster.TeleportCluster,
}
setStringExtensionInCluster(config.Clusters[contextName], extProfileName, localProxyValues.TeleportProfileName)
setStringExtensionInCluster(config.Clusters[contextName], extTeleClusterName, cluster.TeleportCluster)
+2 -2
View File
@@ -185,9 +185,9 @@ func TestLocalProxy(t *testing.T) {
// Check for root-cluster-kube1.
wantConfig.Clusters["root-cluster-kube1"] = &clientcmdapi.Cluster{
ProxyURL: "http://localhost:12345",
Server: rootKubeClusterAddr,
Server: rootKubeClusterAddr + "/v1/teleport/cm9vdC1jbHVzdGVy/a3ViZTE",
CertificateAuthorityData: caData,
TLSServerName: "6b75626531.root-cluster",
TLSServerName: rootClusterName,
LocationOfOrigin: kubeconfigPath,
Extensions: map[string]runtime.Object{
extProfileName: &runtime.Unknown{
+27 -26
View File
@@ -19,38 +19,39 @@
package common
import (
"encoding/hex"
"fmt"
"encoding/base64"
"strings"
"github.com/gravitational/trace"
)
// KubeLocalProxySNI generates the SNI used for Kube local proxy.
func KubeLocalProxySNI(teleportCluster, kubeCluster string) string {
// Hex encode to hide "." in kube cluster name so wildcard cert can be used:
// <hex-encoded-kube-cluster>.<teleport-cluster>
return fmt.Sprintf("%s.%s", hex.EncodeToString([]byte(kubeCluster)), teleportCluster)
// KubeLocalProxyPathPrefix returns the kubeconfig `server:` URL suffix that
// encodes the (teleport cluster, kube cluster) pair for URL-based routing.
// Format: /v1/teleport/<base64url(teleport)>/<base64url(kube)>. Mirrors the
// path consumed by the Teleport proxy's single-cert kube handler and the
// format emitted by tbot v2 (lib/tbot/services/k8s/output_v2.go).
func KubeLocalProxyPathPrefix(teleportCluster, kubeCluster string) string {
return "/v1/teleport/" +
base64.RawURLEncoding.EncodeToString([]byte(teleportCluster)) + "/" +
base64.RawURLEncoding.EncodeToString([]byte(kubeCluster))
}
// TeleportClusterFromKubeLocalProxySNI returns Teleport cluster name from SNI.
func TeleportClusterFromKubeLocalProxySNI(serverName string) string {
_, teleportCluster, _ := strings.Cut(serverName, ".")
return teleportCluster
}
// KubeClusterFromKubeLocalProxySNI returns Kubernetes cluster name from SNI.
func KubeClusterFromKubeLocalProxySNI(serverName string) (string, error) {
kubeCluster, _, _ := strings.Cut(serverName, ".")
str, err := hex.DecodeString(kubeCluster)
if err != nil {
return "", trace.Wrap(err)
// ClustersFromKubeLocalProxyPath parses the leading
// /v1/teleport/<b64>/<b64> prefix of a request URL path and returns the
// decoded (teleport cluster, kube cluster) pair.
func ClustersFromKubeLocalProxyPath(path string) (teleportCluster, kubeCluster string, err error) {
trimmed := strings.TrimPrefix(path, "/")
parts := strings.SplitN(trimmed, "/", 5)
if len(parts) < 4 || parts[0] != "v1" || parts[1] != "teleport" {
return "", "", trace.BadParameter("invalid kube local proxy path %q", path)
}
return string(str), nil
}
// KubeLocalProxyWildcardDomain returns the wildcard domain used to generate
// local self-signed CA for provided Teleport cluster.
func KubeLocalProxyWildcardDomain(teleportCluster string) string {
return "*." + teleportCluster
tcBytes, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return "", "", trace.Wrap(err, "decoding teleport cluster name from path")
}
kcBytes, err := base64.RawURLEncoding.DecodeString(parts[3])
if err != nil {
return "", "", trace.Wrap(err, "decoding kube cluster name from path")
}
return string(tcBytes), string(kcBytes), nil
}
+107
View File
@@ -0,0 +1,107 @@
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package common
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestKubeLocalProxyPathPrefix(t *testing.T) {
tests := []struct {
name string
teleportCluster string
kubeCluster string
want string
}{
{
name: "short names",
teleportCluster: "root-cluster",
kubeCluster: "kube1",
want: "/v1/teleport/cm9vdC1jbHVzdGVy/a3ViZTE",
},
{
name: "long kube cluster name (regression for #61439)",
teleportCluster: "teleport.example.com",
kubeCluster: "loooooooooooooooooooooooooooooooooooooooooong-kube-cluster-exceeding-sixty-three-chars",
want: "/v1/teleport/dGVsZXBvcnQuZXhhbXBsZS5jb20/bG9vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb25nLWt1YmUtY2x1c3Rlci1leGNlZWRpbmctc2l4dHktdGhyZWUtY2hhcnM",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, KubeLocalProxyPathPrefix(tc.teleportCluster, tc.kubeCluster))
})
}
}
func TestClustersFromKubeLocalProxyPath(t *testing.T) {
t.Run("round-trip", func(t *testing.T) {
const teleportCluster = "teleport.example.com"
const kubeCluster = "loooooooooooooooooooooooooooooooooooooooooong-kube-cluster-exceeding-sixty-three-chars"
path := KubeLocalProxyPathPrefix(teleportCluster, kubeCluster) + "/api/v1/namespaces"
tc, kc, err := ClustersFromKubeLocalProxyPath(path)
require.NoError(t, err)
require.Equal(t, teleportCluster, tc)
require.Equal(t, kubeCluster, kc)
})
t.Run("just the prefix", func(t *testing.T) {
path := KubeLocalProxyPathPrefix("root-cluster", "kube1")
tc, kc, err := ClustersFromKubeLocalProxyPath(path)
require.NoError(t, err)
require.Equal(t, "root-cluster", tc)
require.Equal(t, "kube1", kc)
})
t.Run("prefix with trailing slash", func(t *testing.T) {
path := KubeLocalProxyPathPrefix("root-cluster", "kube1") + "/"
tc, kc, err := ClustersFromKubeLocalProxyPath(path)
require.NoError(t, err)
require.Equal(t, "root-cluster", tc)
require.Equal(t, "kube1", kc)
})
t.Run("rejects bare API path", func(t *testing.T) {
_, _, err := ClustersFromKubeLocalProxyPath("/api/v1/namespaces")
require.ErrorContains(t, err, "invalid kube local proxy path")
})
t.Run("rejects wrong leading component", func(t *testing.T) {
_, _, err := ClustersFromKubeLocalProxyPath("/v2/teleport/abc/def/api/v1/namespaces")
require.ErrorContains(t, err, "invalid kube local proxy path")
})
t.Run("rejects missing kube cluster segment", func(t *testing.T) {
_, _, err := ClustersFromKubeLocalProxyPath("/v1/teleport/abc")
require.ErrorContains(t, err, "invalid kube local proxy path")
})
t.Run("rejects non-base64url teleport cluster", func(t *testing.T) {
_, _, err := ClustersFromKubeLocalProxyPath("/v1/teleport/not*base64/a3ViZTE")
require.ErrorContains(t, err, "decoding teleport cluster")
})
t.Run("rejects non-base64url kube cluster", func(t *testing.T) {
_, _, err := ClustersFromKubeLocalProxyPath("/v1/teleport/cm9vdC1jbHVzdGVy/not*base64")
require.ErrorContains(t, err, "decoding kube cluster")
})
}
+57 -47
View File
@@ -58,12 +58,17 @@ const certReissueClientWait = time.Second * 3
// we give them longer time to perform the headless login flow.
const certReissueClientWaitHeadless = defaults.HeadlessLoginTimeout
type kubeClusterKey struct {
teleportCluster string
kubeCluster string
}
// KubeClientCerts is a map of Kubernetes client certs.
type KubeClientCerts map[string]tls.Certificate
type KubeClientCerts map[kubeClusterKey]tls.Certificate
// Add adds a tls.Certificate for a kube cluster.
func (c KubeClientCerts) Add(teleportCluster, kubeCluster string, cert tls.Certificate) {
c[common.KubeLocalProxySNI(teleportCluster, kubeCluster)] = cert
c[kubeClusterKey{teleportCluster: teleportCluster, kubeCluster: kubeCluster}] = cert
}
// KubeCertReissuer reissues a client certificate for a Kubernetes cluster.
@@ -175,11 +180,24 @@ func (m *KubeMiddleware) ClearCerts() {
clear(m.certs)
}
// resolveClusterKey extracts the (teleport, kube) cluster pair for req
// from the /v1/teleport/<b64>/<b64> prefix of the URL path.
func (m *KubeMiddleware) resolveClusterKey(req *http.Request) (teleportCluster, kubeCluster string, err error) {
return common.ClustersFromKubeLocalProxyPath(req.URL.Path)
}
// HandleRequest checks if middleware has valid certificate for this request and
// reissues it if needed. In case of reissuing error we write directly to the response and return true,
// so caller won't continue processing the request.
func (m *KubeMiddleware) HandleRequest(rw http.ResponseWriter, req *http.Request) bool {
cert, err := m.getCertForRequest(req)
teleportCluster, kubeCluster, err := m.resolveClusterKey(req)
if err != nil {
m.logger.WarnContext(req.Context(), "Invalid kube local proxy request", "path", req.URL.Path, "error", err)
trace.WriteError(rw, trace.Wrap(err))
return true
}
cert, err := m.getCert(teleportCluster, kubeCluster)
// If the cert is cleared using m.ClearCerts(), it won't be found.
// This forces the middleware to issue a new cert on a new request.
// This is used in access requests in Connect where we want to refresh certs without closing the proxy.
@@ -187,7 +205,7 @@ func (m *KubeMiddleware) HandleRequest(rw http.ResponseWriter, req *http.Request
return false
}
err = m.reissueCertIfExpired(req.Context(), cert, req.TLS.ServerName)
err = m.reissueCertIfExpired(req.Context(), cert, teleportCluster, kubeCluster)
if err != nil {
// If user input is required we return an error that will try to get user attention to the local proxy
if errors.Is(err, ErrUserInputRequired) {
@@ -205,7 +223,10 @@ func (m *KubeMiddleware) HandleRequest(rw http.ResponseWriter, req *http.Request
}, m.logger)
return true
}
m.logger.WarnContext(req.Context(), "Failed to reissue certificate for server", "server", req.TLS.ServerName)
m.logger.WarnContext(req.Context(), "Failed to reissue certificate",
"teleport_cluster", teleportCluster,
"kube_cluster", kubeCluster,
)
trace.WriteError(rw, trace.Wrap(err))
return true
}
@@ -214,41 +235,41 @@ func (m *KubeMiddleware) HandleRequest(rw http.ResponseWriter, req *http.Request
}
// GetServerName implements [LocalProxyHTTPMiddleware].
// In relay mode it returns the per-cluster SNI the upstream relay uses to identify the target kube cluster.
func (m *KubeMiddleware) GetServerName(req *http.Request) (string, bool, error) {
if !m.relay {
// if we're not connecting to a Relay we should use the standard SNI
// configured in the LocalProxy
return "", false, nil
}
if req.TLS == nil {
return "", false, trace.BadParameter("expected a https request with a TLS connection state")
}
teleportCluster := common.TeleportClusterFromKubeLocalProxySNI(req.TLS.ServerName)
if teleportCluster == "" {
return "", false, trace.BadParameter("invalid Teleport cluster name in SNI %+q", req.TLS.ServerName)
}
kubeCluster, err := common.KubeClusterFromKubeLocalProxySNI(req.TLS.ServerName)
tc, kc, err := m.resolveClusterKey(req)
if err != nil {
return "", false, trace.BadParameter("invalid Kubernetes cluster name in SNI %+q", req.TLS.ServerName)
return "", false, trace.Wrap(err)
}
return kuberelay.FullSNIForKubeCluster(tc, kc), true, nil
}
return kuberelay.FullSNIForKubeCluster(teleportCluster, kubeCluster), true, nil
// getCert looks up the per-cluster client cert to use for an outbound kube
// API request. Clusters are identified by the (teleport, kube) pair parsed
// from the request URL path.
func (m *KubeMiddleware) getCert(teleportCluster, kubeCluster string) (tls.Certificate, error) {
key := kubeClusterKey{teleportCluster: teleportCluster, kubeCluster: kubeCluster}
m.certsMu.RLock()
cert, ok := m.certs[key]
m.certsMu.RUnlock()
if !ok {
return tls.Certificate{}, trace.NotFound("no client cert found for teleport cluster %q kube cluster %q", teleportCluster, kubeCluster)
}
return cert, nil
}
func (m *KubeMiddleware) getCertForRequest(req *http.Request) (tls.Certificate, error) {
if req.TLS == nil {
return tls.Certificate{}, trace.BadParameter("expect a TLS request")
tc, kc, err := m.resolveClusterKey(req)
if err != nil {
return tls.Certificate{}, trace.Wrap(err)
}
m.certsMu.RLock()
cert, ok := m.certs[req.TLS.ServerName]
m.certsMu.RUnlock()
if !ok {
return tls.Certificate{}, trace.NotFound("no client cert found for %v", req.TLS.ServerName)
}
return cert, nil
return m.getCert(tc, kc)
}
// GetClientCerts implements [LocalProxyHTTPMiddleware].
@@ -263,9 +284,9 @@ func (m *KubeMiddleware) GetClientCerts(req *http.Request) ([]tls.Certificate, b
// ErrUserInputRequired returned when user's input required to relogin and/or reissue new certificate.
var ErrUserInputRequired = errors.New("user input required")
// reissueCertIfExpired checks if provided certificate has expired and reissues it if needed and replaces in the middleware certs.
// serverName has a form of <hex-encoded-kube-cluster>.<teleport-cluster>.
func (m *KubeMiddleware) reissueCertIfExpired(ctx context.Context, cert tls.Certificate, serverName string) error {
// reissueCertIfExpired checks if provided certificate has expired and
// reissues it if needed, replacing the entry in the middleware cert map.
func (m *KubeMiddleware) reissueCertIfExpired(ctx context.Context, cert tls.Certificate, teleportCluster, kubeCluster string) error {
needsReissue := false
if len(cert.Certificate) == 0 {
m.logger.InfoContext(ctx, "missing TLS certificate, attempting to reissue a new one")
@@ -286,17 +307,6 @@ func (m *KubeMiddleware) reissueCertIfExpired(ctx context.Context, cert tls.Cert
if m.certReissuer == nil {
return trace.BadParameter("can't reissue proxy certificate - reissuer is not available")
}
teleportCluster := common.TeleportClusterFromKubeLocalProxySNI(serverName)
if teleportCluster == "" {
return trace.BadParameter("can't reissue proxy certificate - teleport cluster is empty")
}
kubeCluster, err := common.KubeClusterFromKubeLocalProxySNI(serverName)
if err != nil {
return trace.Wrap(err, "can't reissue proxy certificate - kube cluster name is invalid")
}
if kubeCluster == "" {
return trace.BadParameter("can't reissue proxy certificate - kube cluster is empty")
}
errCh := make(chan error, 1)
// We start cert reissuing (with relogin if required) only if it's not running already.
@@ -309,7 +319,7 @@ func (m *KubeMiddleware) reissueCertIfExpired(ctx context.Context, cert tls.Cert
newCert, err := m.certReissuer(m.closeContext, teleportCluster, kubeCluster)
if err == nil {
m.certsMu.Lock()
m.certs[serverName] = newCert
m.certs.Add(teleportCluster, kubeCluster, newCert)
m.certsMu.Unlock()
}
errCh <- err
@@ -355,11 +365,11 @@ func NewKubeListener(casByTeleportCluster map[string]tls.Certificate) (net.Liste
}
listener, err := tls.Listen("tcp", "localhost:0", &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
config, ok := configs[common.TeleportClusterFromKubeLocalProxySNI(hello.ServerName)]
if !ok {
return nil, trace.BadParameter("unknown Teleport cluster or invalid TLS server name %v", hello.ServerName)
sni := hello.ServerName
if config, ok := configs[sni]; ok {
return config, nil
}
return config, nil
return nil, trace.BadParameter("unknown Teleport cluster or invalid TLS server name %v", sni)
},
})
return listener, trace.Wrap(err)
@@ -425,7 +435,7 @@ func NewKubeForwardProxy(config KubeForwardProxyConfig) (*ForwardProxy, error) {
func CreateKubeLocalCAs(key *keys.PrivateKey, teleportClusters []string) (map[string]tls.Certificate, error) {
cas := make(map[string]tls.Certificate)
for _, teleportCluster := range teleportClusters {
ca, err := createLocalCA(key, time.Now().Add(defaults.CATTL), common.KubeLocalProxyWildcardDomain(teleportCluster))
ca, err := createLocalCA(key, time.Now().Add(defaults.CATTL), teleportCluster)
if err != nil {
return nil, trace.Wrap(err)
}
+11 -7
View File
@@ -509,10 +509,10 @@ func TestKubeMiddleware(t *testing.T) {
}
t.Run("expired certificate is still reissued if request context expires", func(t *testing.T) {
reqURL, err := url.Parse("https://example.test" + common.KubeLocalProxyPathPrefix(teleportCluster, "kube1") + "/api/v1/namespaces")
require.NoError(t, err)
req := &http.Request{
TLS: &tls.ConnectionState{
ServerName: common.KubeLocalProxySNI(teleportCluster, "kube1"),
},
URL: reqURL,
}
// we set request context to a context that is already canceled, so handler function will start reissuing
// certificate goroutine and then will exit immediately.
@@ -528,7 +528,7 @@ func TestKubeMiddleware(t *testing.T) {
Clock: clockwork.NewFakeClockAt(now.Add(time.Hour * 2)),
CloseContext: context.Background(),
})
err := km.CheckAndSetDefaults()
err = km.CheckAndSetDefaults()
require.NoError(t, err)
var rw *responsewriters.MemoryResponseWriter
@@ -601,12 +601,16 @@ func TestKubeMiddleware(t *testing.T) {
},
}
mustURL := func(s string) *url.URL {
u, err := url.Parse(s)
require.NoError(t, err)
return u
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
req := http.Request{
TLS: &tls.ConnectionState{
ServerName: common.KubeLocalProxySNI(teleportCluster, tt.reqClusterName),
},
URL: mustURL("https://example.test" + common.KubeLocalProxyPathPrefix(teleportCluster, tt.reqClusterName) + "/api/v1/namespaces"),
}
km := NewKubeMiddleware(KubeMiddlewareConfig{
Certs: tt.startCerts,
+9 -5
View File
@@ -68,7 +68,7 @@ func TestKubeGateway(t *testing.T) {
KubernetesCluster: kubeClusterName,
}
clock := clockwork.NewFakeClock()
proxy := mustStartMockProxyWithKubeAPI(t, identity)
proxy := mustStartMockProxyWithKubeAPI(t, identity, teleportClusterName, kubeClusterName)
gateway, err := New(
Config{
Clock: clock,
@@ -157,10 +157,10 @@ func kubeClientForLocalProxy(t *testing.T, kubeconfigPath, teleportCluster, kube
CAData: kubeCAPEM,
CertData: clientCertPEM,
KeyData: config.AuthInfos[contextName].ClientKeyData,
ServerName: common.KubeLocalProxySNI(teleportCluster, kubeCluster),
ServerName: teleportCluster,
}
client, err := kubernetes.NewForConfig(&rest.Config{
Host: "https://" + teleportCluster,
Host: "https://" + teleportCluster + common.KubeLocalProxyPathPrefix(teleportCluster, kubeCluster),
TLSClientConfig: tlsClientConfig,
Proxy: http.ProxyURL(proxyURL),
})
@@ -204,7 +204,7 @@ func (m *mockProxyWithKubeAPI) certPool() *x509.CertPool {
return certPool
}
func mustStartMockProxyWithKubeAPI(t *testing.T, identity tlsca.Identity) *mockProxyWithKubeAPI {
func mustStartMockProxyWithKubeAPI(t *testing.T, identity tlsca.Identity, teleportCluster, kubeCluster string) *mockProxyWithKubeAPI {
t.Helper()
netListener, err := net.Listen("tcp", "localhost:0")
@@ -231,7 +231,11 @@ func mustStartMockProxyWithKubeAPI(t *testing.T, identity tlsca.Identity) *mockP
ClientCAs: m.certPool(),
})
go func() {
err := http.Serve(tlsListener, mockKubeAPIHandler(t))
// Stand in for the Teleport proxy's singleCertHandler route, which
// consumes the /v1/teleport/<b64>/<b64> prefix before dispatching to
// the kube API handlers.
handler := http.StripPrefix(common.KubeLocalProxyPathPrefix(teleportCluster, kubeCluster), mockKubeAPIHandler(t))
err := http.Serve(tlsListener, handler)
if err != nil && !errors.Is(err, net.ErrClosed) {
assert.NoError(t, err)
}
+2 -2
View File
@@ -201,10 +201,10 @@ func sendRequestToKubeLocalProxy(t *testing.T, config *clientcmdapi.Config, tele
CAData: config.Clusters[contextName].CertificateAuthorityData,
CertData: config.AuthInfos[contextName].ClientCertificateData,
KeyData: config.AuthInfos[contextName].ClientKeyData,
ServerName: common.KubeLocalProxySNI(teleportCluster, kubeCluster),
ServerName: teleportCluster,
}
restConfig := &rest.Config{
Host: "https://" + teleportCluster,
Host: "https://" + teleportCluster + common.KubeLocalProxyPathPrefix(teleportCluster, kubeCluster),
TLSClientConfig: tlsClientConfig,
Proxy: http.ProxyURL(proxyURL),
}