From 5e6fc5dce8b12c5ce80e016b208a51c81a8c9ce8 Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Tue, 12 Feb 2019 23:37:01 -0800 Subject: [PATCH] Limit the number of operations in a single json patch to be 10,000 --- .../apiserver/pkg/endpoints/handlers/patch.go | 10 +++ test/integration/apiserver/BUILD | 1 + .../max_json_patch_operations_test.go | 69 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 test/integration/apiserver/max_json_patch_operations_test.go diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go index 4c2fdeea80..b257eadd49 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go @@ -49,6 +49,11 @@ import ( utiltrace "k8s.io/utils/trace" ) +const ( + // maximum number of operations a single json patch may contain. + maxJSONPatchOperations = 10000 +) + // PatchResource returns a function that will handle a resource patch. func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { @@ -331,6 +336,11 @@ func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr if err != nil { return nil, errors.NewBadRequest(err.Error()) } + if len(patchObj) > maxJSONPatchOperations { + return nil, errors.NewRequestEntityTooLargeError( + fmt.Sprintf("The allowed maximum operations in a JSON patch is %d, got %d", + maxJSONPatchOperations, len(patchObj))) + } patchedJS, err := patchObj.Apply(versionedJS) if err != nil { return nil, errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false) diff --git a/test/integration/apiserver/BUILD b/test/integration/apiserver/BUILD index 65818fc4b4..5825d88586 100644 --- a/test/integration/apiserver/BUILD +++ b/test/integration/apiserver/BUILD @@ -11,6 +11,7 @@ go_test( srcs = [ "apiserver_test.go", "main_test.go", + "max_json_patch_operations_test.go", "max_request_body_bytes_test.go", "patch_test.go", "print_test.go", diff --git a/test/integration/apiserver/max_json_patch_operations_test.go b/test/integration/apiserver/max_json_patch_operations_test.go new file mode 100644 index 0000000000..0785428862 --- /dev/null +++ b/test/integration/apiserver/max_json_patch_operations_test.go @@ -0,0 +1,69 @@ +/* +Copyright 2018 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 apiserver + +import ( + "fmt" + "strings" + "testing" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/cmd/kube-apiserver/app/options" + "k8s.io/kubernetes/test/integration/framework" +) + +// Tests that the apiserver limits the number of operations in a json patch. +func TestMaxJSONPatchOperations(t *testing.T) { + stopCh := make(chan struct{}) + defer close(stopCh) + clientSet, _ := framework.StartTestServer(t, stopCh, framework.TestServerSetup{ + ModifyServerRunOptions: func(opts *options.ServerRunOptions) { + opts.GenericServerRunOptions.MaxRequestBodyBytes = 1024 * 1024 + }, + }) + + p := `{"op":"add","path":"/x","value":"y"}` + // maxJSONPatchOperations = 10000 + hugePatch := []byte("[" + strings.Repeat(p+",", 10000) + p + "]") + + c := clientSet.CoreV1().RESTClient() + // Create a secret so we can patch it. + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + } + _, err := clientSet.CoreV1().Secrets("default").Create(secret) + if err != nil { + t.Fatal(err) + } + + err = c.Patch(types.JSONPatchType).AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")). + Body(hugePatch).Do().Error() + if err == nil { + t.Fatalf("unexpected no error") + } + if !errors.IsRequestEntityTooLargeError(err) { + t.Errorf("expected requested entity too large err, got %v", err) + } + if !strings.Contains(err.Error(), "The allowed maximum operations in a JSON patch is") { + t.Errorf("expected the error message to be about maximum operations, got %v", err) + } +}