Skip to content

Commit

Permalink
http2: trim connetions and buffers
Browse files Browse the repository at this point in the history
Set max streams per connection to 100.
Limit frame size and buffer to 256 kb.
Limit connection buffer to 256 kb * 100.
  • Loading branch information
ibihim committed Oct 19, 2023
1 parent 7a0795e commit 569f97f
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 3 deletions.
21 changes: 18 additions & 3 deletions cmd/kube-rbac-proxy/app/kube-rbac-proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ type completedProxyRunOptions struct {
upstreamForceH2C bool
upstreamCABundle *x509.CertPool

http2Options *http2.Server

auth *proxy.Config
tls *options.TLSConfig

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions pkg/filters/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}

0 comments on commit 569f97f

Please sign in to comment.