From f589c1213c8ba4fa0e31c523b2e9dcc27298084f Mon Sep 17 00:00:00 2001 From: David Eads Date: Mon, 26 Aug 2019 09:39:29 -0400 Subject: [PATCH] add cache-control headers to kube-apiserver --- cmd/controller-manager/app/serve.go | 4 +- cmd/kube-scheduler/app/server.go | 1 + pkg/kubeapiserver/server/insecure_handler.go | 1 + .../apiserver/pkg/endpoints/filters/BUILD | 2 + .../pkg/endpoints/filters/cachecontrol.go | 33 ++++++++ .../endpoints/filters/cachecontrol_test.go | 76 +++++++++++++++++++ .../src/k8s.io/apiserver/pkg/server/config.go | 1 + 7 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol_test.go diff --git a/cmd/controller-manager/app/serve.go b/cmd/controller-manager/app/serve.go index 86e7e4136ef..81b01329ddc 100644 --- a/cmd/controller-manager/app/serve.go +++ b/cmd/controller-manager/app/serve.go @@ -17,10 +17,11 @@ limitations under the License. package app import ( - "github.com/prometheus/client_golang/prometheus" "net/http" goruntime "runtime" + "github.com/prometheus/client_golang/prometheus" + genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" apirequest "k8s.io/apiserver/pkg/endpoints/request" apiserver "k8s.io/apiserver/pkg/server" @@ -46,6 +47,7 @@ func BuildHandlerChain(apiHandler http.Handler, authorizationInfo *apiserver.Aut handler = genericapifilters.WithAuthentication(handler, authenticationInfo.Authenticator, failedHandler, nil) } handler = genericapifilters.WithRequestInfo(handler, requestInfoResolver) + handler = genericapifilters.WithCacheControl(handler) handler = genericfilters.WithPanicRecovery(handler) return handler diff --git a/cmd/kube-scheduler/app/server.go b/cmd/kube-scheduler/app/server.go index 45feb3bfd4a..dd3326e035d 100644 --- a/cmd/kube-scheduler/app/server.go +++ b/cmd/kube-scheduler/app/server.go @@ -287,6 +287,7 @@ func buildHandlerChain(handler http.Handler, authn authenticator.Request, authz handler = genericapifilters.WithAuthorization(handler, authz, legacyscheme.Codecs) handler = genericapifilters.WithAuthentication(handler, authn, failedHandler, nil) handler = genericapifilters.WithRequestInfo(handler, requestInfoResolver) + handler = genericapifilters.WithCacheControl(handler) handler = genericfilters.WithPanicRecovery(handler) return handler diff --git a/pkg/kubeapiserver/server/insecure_handler.go b/pkg/kubeapiserver/server/insecure_handler.go index 850353538b1..bbdb8d3b45e 100644 --- a/pkg/kubeapiserver/server/insecure_handler.go +++ b/pkg/kubeapiserver/server/insecure_handler.go @@ -34,6 +34,7 @@ func BuildInsecureHandlerChain(apiHandler http.Handler, c *server.Config) http.H handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc) handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup) handler = genericapifilters.WithRequestInfo(handler, server.NewRequestInfoResolver(c)) + handler = genericapifilters.WithCacheControl(handler) handler = genericfilters.WithPanicRecovery(handler) return handler diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD index 0fc8e02381b..8997ccae463 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD @@ -13,6 +13,7 @@ go_test( "authentication_test.go", "authn_audit_test.go", "authorization_test.go", + "cachecontrol_test.go", "impersonation_test.go", "requestinfo_test.go", ], @@ -44,6 +45,7 @@ go_library( "authentication.go", "authn_audit.go", "authorization.go", + "cachecontrol.go", "doc.go", "impersonation.go", "requestinfo.go", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol.go new file mode 100644 index 00000000000..e19f9d055fd --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol.go @@ -0,0 +1,33 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filters + +import ( + "net/http" +) + +// WithCacheControl sets the Cache-Control header to "no-cache, private" because all servers are protected by authn/authz. +// see https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#defining_optimal_cache-control_policy +func WithCacheControl(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Set the cache-control header if it is not already set + if _, ok := w.Header()["Cache-Control"]; !ok { + w.Header().Set("Cache-Control", "no-cache, private") + } + handler.ServeHTTP(w, req) + }) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol_test.go new file mode 100644 index 00000000000..1bfdf0a1aa8 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filters + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestCacheControl(t *testing.T) { + tests := []struct { + name string + path string + + startingHeader string + expectedHeader string + }{ + { + name: "simple", + path: "/api/v1/namespaces", + expectedHeader: "no-cache, private", + }, + { + name: "openapi", + path: "/openapi/v2", + expectedHeader: "no-cache, private", + }, + { + name: "already-set", + path: "/api/v1/namespaces", + startingHeader: "nonsense", + expectedHeader: "nonsense", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + handler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + //do nothing + }) + wrapped := WithCacheControl(handler) + + testRequest, err := http.NewRequest(http.MethodGet, test.path, nil) + if err != nil { + t.Fatal(err) + } + w := httptest.NewRecorder() + if len(test.startingHeader) > 0 { + w.Header().Set("Cache-Control", test.startingHeader) + } + + wrapped.ServeHTTP(w, testRequest) + actual := w.Header().Get("Cache-Control") + + if actual != test.expectedHeader { + t.Fatal(actual) + } + }) + } + +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index f3e5f6e0407..b62ecf0ac03 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -599,6 +599,7 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler { handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc, c.RequestTimeout) handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup) handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver) + handler = genericapifilters.WithCacheControl(handler) handler = genericfilters.WithPanicRecovery(handler) return handler }