m/n/k/authproxy: make use of SPDY through proxy work
Kubernetes still uses SPDY for interactive/streaming-type calls (like
exec or port-forward). Our proxy uses a HTTP/2 backend connection to
Kubernetes's API server. A HTTP/2 stream cannot be upgraded to SPDY
meaning these API requests all fail. This implements a slightly ugly
workaround by using two HTTP transports, a regular transport which
supports HTTP/2 and a fallback transport which does not. The proxy
selects the fallback transport if it detects that the request is trying
to upgrade to SPDY.
Change-Id: Idd44f58d07ec5570ddf8941ae7595225f47f254d
Reviewed-on: https://review.monogon.dev/c/monogon/+/645
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/kubernetes/authproxy/authproxy.go b/metropolis/node/kubernetes/authproxy/authproxy.go
index 0477b88..957cb8a 100644
--- a/metropolis/node/kubernetes/authproxy/authproxy.go
+++ b/metropolis/node/kubernetes/authproxy/authproxy.go
@@ -67,11 +67,16 @@
return err
}
- proxy := httputil.NewSingleHostReverseProxy(&url.URL{
- Host: net.JoinHostPort("localhost", node.KubernetesAPIPort.PortString()),
+ internalAPIServer := net.JoinHostPort("localhost", node.KubernetesAPIPort.PortString())
+ standardProxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
+ Host: internalAPIServer,
})
- proxy.Transport = &http.Transport{
+ noHTTP2Proxy := httputil.NewSingleHostReverseProxy(&url.URL{
+ Scheme: "https",
+ Host: internalAPIServer,
+ })
+ transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
@@ -87,7 +92,12 @@
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
- proxy.ErrorHandler = func(w http.ResponseWriter, req *http.Request, err error) {
+ standardProxy.Transport = transport
+ noHTTP2Transport := transport.Clone()
+ noHTTP2Transport.ForceAttemptHTTP2 = false
+ noHTTP2Transport.TLSClientConfig.NextProtos = []string{"http/1.1"}
+ noHTTP2Proxy.Transport = noHTTP2Transport
+ errorHandler := func(w http.ResponseWriter, req *http.Request, err error) {
logger.Infof("Proxy error: %v", err)
respondWithK8sStatus(w, &metav1.Status{
Status: metav1.StatusFailure,
@@ -96,6 +106,8 @@
Message: "authproxy could not reach apiserver",
})
}
+ standardProxy.ErrorHandler = errorHandler
+ noHTTP2Proxy.ErrorHandler = errorHandler
serverCert, err := s.getTLSCert(ctx, pki.APIServer)
if err != nil {
@@ -130,6 +142,13 @@
})
return
}
+ proxyToUse := standardProxy
+ // Kubernetes wants to use SPDY but using SPDY with HTTP/2 is unsupported.
+ // SPDY should be removed from K8s, this is tracked in
+ // https://github.com/kubernetes/kubernetes/issues/7452
+ if strings.HasPrefix(strings.ToLower(req.Header.Get("Upgrade")), "spdy/") {
+ proxyToUse = noHTTP2Proxy
+ }
// Clone the request as otherwise modifying it is not allowed
newReq := req.Clone(req.Context())
// Drop any X-Remote headers to prevent injection
@@ -141,7 +160,7 @@
newReq.Header.Set("X-Remote-User", clientIdentity)
newReq.Header.Set("X-Remote-Group", "")
- proxy.ServeHTTP(rw, newReq)
+ proxyToUse.ServeHTTP(rw, newReq)
}),
}
go server.ListenAndServeTLS("", "")