diff --git a/integration/proxy/proxy_helpers.go b/integration/proxy/proxy_helpers.go index 834ad5310389c..eb648fc3fe25a 100644 --- a/integration/proxy/proxy_helpers.go +++ b/integration/proxy/proxy_helpers.go @@ -570,6 +570,7 @@ func mustCreateKubeLocalProxyMiddleware(t *testing.T, teleportCluster, kubeClust CertReissuer: func(ctx context.Context, teleportCluster, kubeCluster string) (tls.Certificate, error) { return tls.Certificate{}, nil }, + CloseContext: context.Background(), }) } diff --git a/lib/srv/alpnproxy/kube.go b/lib/srv/alpnproxy/kube.go index 762bcbbde8ab7..941ee62fe8f0b 100644 --- a/lib/srv/alpnproxy/kube.go +++ b/lib/srv/alpnproxy/kube.go @@ -78,7 +78,8 @@ type KubeMiddleware struct { // headless controls whether proxy is working in headless login mode. headless bool - logger logrus.FieldLogger + logger logrus.FieldLogger + closeContext context.Context // isCertReissuingRunning is used to only ever have one concurrent cert reissuing session requiring user input. isCertReissuingRunning atomic.Bool @@ -94,6 +95,7 @@ type KubeMiddlewareConfig struct { Headless bool Clock clockwork.Clock Logger logrus.FieldLogger + CloseContext context.Context } // NewKubeMiddleware creates a new KubeMiddleware. @@ -104,6 +106,7 @@ func NewKubeMiddleware(cfg KubeMiddlewareConfig) LocalProxyHTTPMiddleware { headless: cfg.Headless, clock: cfg.Clock, logger: cfg.Logger, + closeContext: cfg.CloseContext, } } @@ -118,6 +121,9 @@ func (m *KubeMiddleware) CheckAndSetDefaults() error { if m.logger == nil { m.logger = logrus.WithField(trace.Component, "local_proxy_kube") } + if m.closeContext == nil { + return trace.BadParameter("missing close context") + } return nil } @@ -242,7 +248,7 @@ func (m *KubeMiddleware) reissueCertIfExpired(ctx context.Context, cert tls.Cert if identity.RouteToCluster != "" { cluster = identity.RouteToCluster } - newCert, err := m.certReissuer(ctx, cluster, identity.KubernetesCluster) + newCert, err := m.certReissuer(m.closeContext, cluster, identity.KubernetesCluster) if err == nil { m.certsMu.Lock() m.certs[serverName] = newCert diff --git a/lib/srv/alpnproxy/local_proxy_test.go b/lib/srv/alpnproxy/local_proxy_test.go index 41ac1b948222d..137c698d38df2 100644 --- a/lib/srv/alpnproxy/local_proxy_test.go +++ b/lib/srv/alpnproxy/local_proxy_test.go @@ -502,9 +502,52 @@ func TestKubeMiddleware(t *testing.T) { ) certReissuer = func(ctx context.Context, teleportCluster, kubeCluster string) (tls.Certificate, error) { - return newCert, nil + select { + case <-ctx.Done(): + return tls.Certificate{}, ctx.Err() + default: + return newCert, nil + } } + t.Run("expired certificate is still reissued if request context expires", func(t *testing.T) { + req := &http.Request{ + TLS: &tls.ConnectionState{ + ServerName: "kube1", + }, + } + // we set request context to a context that will expired immediately. + reqCtx, cancel := context.WithDeadline(context.Background(), time.Now()) + defer cancel() + req = req.WithContext(reqCtx) + + km := NewKubeMiddleware(KubeMiddlewareConfig{ + Certs: KubeClientCerts{"kube1": kube1Cert}, + CertReissuer: certReissuer, + Clock: clockwork.NewFakeClockAt(now.Add(time.Hour * 2)), + CloseContext: context.Background(), + }) + err := km.CheckAndSetDefaults() + require.NoError(t, err) + + rw := responsewriters.NewMemoryResponseWriter() + // HandleRequest will reissue certificate if needed. + km.HandleRequest(rw, req) + + // request timed out. + require.Equal(t, http.StatusInternalServerError, rw.Status()) + require.Contains(t, rw.Buffer().String(), "context deadline exceeded") + + // just let the reissuing goroutine some time to replace certs. + time.Sleep(10 * time.Millisecond) + + // but certificate still was reissued. + certs, err := km.OverwriteClientCerts(req) + require.NoError(t, err) + require.Len(t, certs, 1) + require.Equal(t, newCert, certs[0], "certificate was not reissued") + }) + testCases := []struct { name string reqClusterName string @@ -557,6 +600,7 @@ func TestKubeMiddleware(t *testing.T) { Certs: tt.startCerts, CertReissuer: certReissuer, Clock: tt.clock, + CloseContext: context.Background(), }) // HandleRequest will reissue certificate if needed diff --git a/lib/teleterm/gateway/kube.go b/lib/teleterm/gateway/kube.go index 55223dd135345..2ac70892584f0 100644 --- a/lib/teleterm/gateway/kube.go +++ b/lib/teleterm/gateway/kube.go @@ -142,6 +142,7 @@ func (k *kube) makeKubeMiddleware() (alpnproxy.LocalProxyHTTPMiddleware, error) CertReissuer: certReissuer.reissueCert, Clock: k.cfg.Clock, Logger: k.cfg.Log, + CloseContext: k.closeContext, }), nil } diff --git a/tool/tsh/common/kube_proxy.go b/tool/tsh/common/kube_proxy.go index c39bc50f4e928..1fe163db1ff4e 100644 --- a/tool/tsh/common/kube_proxy.go +++ b/tool/tsh/common/kube_proxy.go @@ -339,6 +339,7 @@ func makeKubeLocalProxy(cf *CLIConf, tc *client.TeleportClient, clusters kubecon CertReissuer: kubeProxy.getCertReissuer(tc), Headless: cf.Headless, Logger: log, + CloseContext: cf.Context, }) localProxy, err := alpnproxy.NewLocalProxy(