diff --git a/cmd/kube-rbac-proxy/app/kube-rbac-proxy.go b/cmd/kube-rbac-proxy/app/kube-rbac-proxy.go index 85fb11603..94d41fbde 100644 --- a/cmd/kube-rbac-proxy/app/kube-rbac-proxy.go +++ b/cmd/kube-rbac-proxy/app/kube-rbac-proxy.go @@ -135,6 +135,8 @@ type completedProxyRunOptions struct { upstreamForceH2C bool upstreamCABundle *x509.CertPool + http2Options *http2.Server + auth *proxy.Config tls *options.TLSConfig @@ -251,6 +253,19 @@ func Complete(o *options.ProxyRunOptions) (*completedProxyRunOptions, error) { return nil, fmt.Errorf("failed to instantiate Kubernetes client: %w", err) } + // At least 99% of serialized resources in surveyed clusters were smaller than 256kb. + // This should be big enough to accommodate most API POST requests in a single frame, + // and small enough to allow a per connection buffer of this size multiplied by `MaxConcurrentStreams`. + // https://github.com/kubernetes/kubernetes/blob/c3809672aa7b437ee3be4e227c182f0ef5f5e2b9/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go#L175C1-L206 + const resourceBody99Percentile = 256 * 1024 + completed.http2Options = &http2.Server{ + IdleTimeout: 90 * time.Second, + MaxConcurrentStreams: 100, + MaxReadFrameSize: resourceBody99Percentile, + MaxUploadBufferPerStream: resourceBody99Percentile, + MaxUploadBufferPerConnection: resourceBody99Percentile * 100, + } + return completed, nil } @@ -411,7 +426,7 @@ func Run(cfg *completedProxyRunOptions) error { srv.TLSConfig.MinVersion = version srv.TLSConfig.ClientAuth = tls.RequestClientCert - if err := http2.ConfigureServer(srv, nil); err != nil { + if err := http2.ConfigureServer(srv, cfg.http2Options); err != nil { return fmt.Errorf("failed to configure http2 server: %w", err) } @@ -441,7 +456,7 @@ func Run(cfg *completedProxyRunOptions) error { TLSConfig: srv.TLSConfig.Clone(), } - if err := http2.ConfigureServer(proxyEndpointsSrv, nil); err != nil { + if err := http2.ConfigureServer(proxyEndpointsSrv, cfg.http2Options); err != nil { return fmt.Errorf("failed to configure http2 server: %w", err) } @@ -472,7 +487,7 @@ func Run(cfg *completedProxyRunOptions) error { } { if cfg.insecureListenAddress != "" { - srv := &http.Server{Handler: h2c.NewHandler(mux, &http2.Server{})} + srv := &http.Server{Handler: h2c.NewHandler(mux, cfg.http2Options)} l, err := net.Listen("tcp", cfg.insecureListenAddress) if err != nil { diff --git a/pkg/filters/auth.go b/pkg/filters/auth.go index b3027cc7c..ec6e9addc 100644 --- a/pkg/filters/auth.go +++ b/pkg/filters/auth.go @@ -25,6 +25,7 @@ import ( "github.com/brancz/kube-rbac-proxy/pkg/proxy" "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/klog/v2" @@ -53,6 +54,20 @@ func WithAuthentication( return } + // http2 is an expensive protocol that is prone to abuse, + // see CVE-2023-44487 and CVE-2023-39325 for an example. + // Do not allow unauthenticated clients to keep these + // connections open (i.e. basically degrade them to the + // performance of http1 with keep-alive disabled). + // + // https://github.com/kubernetes/apiserver/blob/master/pkg/endpoints/filters/authentication.go#L107C1-L117C4 + if req.ProtoMajor == 2 && isAnonymousUser(res.User) { + // limit this connection to just this request, + // and then send a GOAWAY and tear down the TCP connection + // https://github.com/golang/net/commit/97aa3a539ec716117a9d15a4659a911f50d13c3c + w.Header().Set("Connection", "close") + } + req = req.WithContext(request.WithUser(req.Context(), res.User)) handler.ServeHTTP(w, req) } @@ -123,3 +138,15 @@ func WithAuthHeaders(cfg *authn.AuthnHeaderConfig, handler http.HandlerFunc) htt handler.ServeHTTP(w, req) } } + +func isAnonymousUser(u user.Info) bool { + if u.GetName() == user.Anonymous { + return true + } + for _, group := range u.GetGroups() { + if group == user.AllUnauthenticated { + return true + } + } + return false +}