Skip to content

Commit 8a2d338

Browse files
author
Chao Xu
committed
Limit the number of operations in a single json patch to be 10,000
1 parent 7d0e020 commit 8a2d338

File tree

3 files changed

+86
-2
lines changed

3 files changed

+86
-2
lines changed

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ import (
4141
utiltrace "k8s.io/apiserver/pkg/util/trace"
4242
)
4343

44+
const (
45+
// maximum number of operations a single json patch may contain.
46+
maxJSONPatchOperations = 10000
47+
)
48+
4449
// PatchResource returns a function that will handle a resource patch.
4550
func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc {
4651
return func(w http.ResponseWriter, req *http.Request) {
@@ -241,9 +246,18 @@ func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr
241246
case types.JSONPatchType:
242247
patchObj, err := jsonpatch.DecodePatch(p.patchJS)
243248
if err != nil {
244-
return nil, err
249+
return nil, errors.NewBadRequest(err.Error())
250+
}
251+
if len(patchObj) > maxJSONPatchOperations {
252+
return nil, errors.NewRequestEntityTooLargeError(
253+
fmt.Sprintf("The allowed maximum operations in a JSON patch is %d, got %d",
254+
maxJSONPatchOperations, len(patchObj)))
255+
}
256+
patchedJS, err := patchObj.Apply(versionedJS)
257+
if err != nil {
258+
return nil, errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
245259
}
246-
return patchObj.Apply(versionedJS)
260+
return patchedJS, nil
247261
case types.MergePatchType:
248262
return jsonpatch.MergePatch(versionedJS, p.patchJS)
249263
default:

test/integration/apiserver/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ go_test(
1111
srcs = [
1212
"apiserver_test.go",
1313
"main_test.go",
14+
"max_json_patch_operations_test.go",
1415
"max_request_body_bytes_test.go",
1516
"patch_test.go",
1617
"print_test.go",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package apiserver
18+
19+
import (
20+
"fmt"
21+
"strings"
22+
"testing"
23+
24+
"k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
29+
"k8s.io/kubernetes/test/integration/framework"
30+
)
31+
32+
// Tests that the apiserver limits the number of operations in a json patch.
33+
func TestMaxJSONPatchOperations(t *testing.T) {
34+
stopCh := make(chan struct{})
35+
defer close(stopCh)
36+
clientSet, _ := framework.StartTestServer(t, stopCh, framework.TestServerSetup{
37+
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
38+
opts.GenericServerRunOptions.MaxRequestBodyBytes = 1024 * 1024
39+
},
40+
})
41+
42+
p := `{"op":"add","path":"/x","value":"y"}`
43+
// maxJSONPatchOperations = 10000
44+
hugePatch := []byte("[" + strings.Repeat(p+",", 10000) + p + "]")
45+
46+
c := clientSet.CoreV1().RESTClient()
47+
// Create a secret so we can patch it.
48+
secret := &v1.Secret{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Name: "test",
51+
},
52+
}
53+
_, err := clientSet.CoreV1().Secrets("default").Create(secret)
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
58+
err = c.Patch(types.JSONPatchType).AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")).
59+
Body(hugePatch).Do().Error()
60+
if err == nil {
61+
t.Fatalf("unexpected no error")
62+
}
63+
if !errors.IsRequestEntityTooLargeError(err) {
64+
t.Errorf("expected requested entity too large err, got %v", err)
65+
}
66+
if !strings.Contains(err.Error(), "The allowed maximum operations in a JSON patch is") {
67+
t.Errorf("expected the error message to be about maximum operations, got %v", err)
68+
}
69+
}

0 commit comments

Comments
 (0)