mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 10:20:51 +00:00
Limit YAML/JSON decode size
This commit is contained in:
parent
8aeffa8716
commit
8ef4566cef
@ -130,8 +130,8 @@ func TestAddFlags(t *testing.T) {
|
|||||||
MaxMutatingRequestsInFlight: 200,
|
MaxMutatingRequestsInFlight: 200,
|
||||||
RequestTimeout: time.Duration(2) * time.Minute,
|
RequestTimeout: time.Duration(2) * time.Minute,
|
||||||
MinRequestTimeout: 1800,
|
MinRequestTimeout: 1800,
|
||||||
JSONPatchMaxCopyBytes: int64(100 * 1024 * 1024),
|
JSONPatchMaxCopyBytes: int64(3 * 1024 * 1024),
|
||||||
MaxRequestBodyBytes: int64(100 * 1024 * 1024),
|
MaxRequestBodyBytes: int64(3 * 1024 * 1024),
|
||||||
},
|
},
|
||||||
Admission: &kubeoptions.AdmissionOptions{
|
Admission: &kubeoptions.AdmissionOptions{
|
||||||
GenericAdmission: &apiserveroptions.AdmissionOptions{
|
GenericAdmission: &apiserveroptions.AdmissionOptions{
|
||||||
|
@ -201,6 +201,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
|
|||||||
c.GenericConfig.RequestTimeout,
|
c.GenericConfig.RequestTimeout,
|
||||||
time.Duration(c.GenericConfig.MinRequestTimeout)*time.Second,
|
time.Duration(c.GenericConfig.MinRequestTimeout)*time.Second,
|
||||||
apiGroupInfo.StaticOpenAPISpec,
|
apiGroupInfo.StaticOpenAPISpec,
|
||||||
|
c.GenericConfig.MaxRequestBodyBytes,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -125,6 +125,10 @@ type crdHandler struct {
|
|||||||
// purpose of managing fields, it is how CR handlers get the structure
|
// purpose of managing fields, it is how CR handlers get the structure
|
||||||
// of TypeMeta and ObjectMeta
|
// of TypeMeta and ObjectMeta
|
||||||
staticOpenAPISpec *spec.Swagger
|
staticOpenAPISpec *spec.Swagger
|
||||||
|
|
||||||
|
// The limit on the request size that would be accepted and decoded in a write request
|
||||||
|
// 0 means no limit.
|
||||||
|
maxRequestBodyBytes int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// crdInfo stores enough information to serve the storage for the custom resource
|
// crdInfo stores enough information to serve the storage for the custom resource
|
||||||
@ -169,7 +173,8 @@ func NewCustomResourceDefinitionHandler(
|
|||||||
authorizer authorizer.Authorizer,
|
authorizer authorizer.Authorizer,
|
||||||
requestTimeout time.Duration,
|
requestTimeout time.Duration,
|
||||||
minRequestTimeout time.Duration,
|
minRequestTimeout time.Duration,
|
||||||
staticOpenAPISpec *spec.Swagger) (*crdHandler, error) {
|
staticOpenAPISpec *spec.Swagger,
|
||||||
|
maxRequestBodyBytes int64) (*crdHandler, error) {
|
||||||
ret := &crdHandler{
|
ret := &crdHandler{
|
||||||
versionDiscoveryHandler: versionDiscoveryHandler,
|
versionDiscoveryHandler: versionDiscoveryHandler,
|
||||||
groupDiscoveryHandler: groupDiscoveryHandler,
|
groupDiscoveryHandler: groupDiscoveryHandler,
|
||||||
@ -185,6 +190,7 @@ func NewCustomResourceDefinitionHandler(
|
|||||||
requestTimeout: requestTimeout,
|
requestTimeout: requestTimeout,
|
||||||
minRequestTimeout: minRequestTimeout,
|
minRequestTimeout: minRequestTimeout,
|
||||||
staticOpenAPISpec: staticOpenAPISpec,
|
staticOpenAPISpec: staticOpenAPISpec,
|
||||||
|
maxRequestBodyBytes: maxRequestBodyBytes,
|
||||||
}
|
}
|
||||||
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: ret.createCustomResourceDefinition,
|
AddFunc: ret.createCustomResourceDefinition,
|
||||||
@ -812,6 +818,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
|
|||||||
TableConvertor: storages[v.Name].CustomResource,
|
TableConvertor: storages[v.Name].CustomResource,
|
||||||
|
|
||||||
Authorizer: r.authorizer,
|
Authorizer: r.authorizer,
|
||||||
|
|
||||||
|
MaxRequestBodyBytes: r.maxRequestBodyBytes,
|
||||||
}
|
}
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||||
reqScope := *requestScopes[v.Name]
|
reqScope := *requestScopes[v.Name]
|
||||||
|
@ -15,6 +15,7 @@ go_test(
|
|||||||
"change_test.go",
|
"change_test.go",
|
||||||
"defaulting_test.go",
|
"defaulting_test.go",
|
||||||
"finalization_test.go",
|
"finalization_test.go",
|
||||||
|
"limit_test.go",
|
||||||
"objectmeta_test.go",
|
"objectmeta_test.go",
|
||||||
"pruning_test.go",
|
"pruning_test.go",
|
||||||
"registration_test.go",
|
"registration_test.go",
|
||||||
|
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
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 integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
|
||||||
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
|
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLimits(t *testing.T) {
|
||||||
|
tearDown, config, _, err := fixtures.StartDefaultServer(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
apiExtensionClient, err := clientset.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
dynamicClient, err := dynamic.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
noxuDefinition := fixtures.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped)
|
||||||
|
noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := noxuDefinition.Spec.Names.Kind
|
||||||
|
apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version
|
||||||
|
|
||||||
|
rest := apiExtensionClient.Discovery().RESTClient()
|
||||||
|
|
||||||
|
// Create YAML over 3MB limit
|
||||||
|
t.Run("create YAML over limit", func(t *testing.T) {
|
||||||
|
yamlBody := []byte(fmt.Sprintf(`
|
||||||
|
apiVersion: %s
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
values: `+strings.Repeat("[", 3*1024*1024), apiVersion, kind))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/yaml").
|
||||||
|
SetHeader("Content-Type", "application/yaml").
|
||||||
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
|
Body(yamlBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsRequestEntityTooLargeError(err) {
|
||||||
|
t.Errorf("expected too large error, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create YAML just under 3MB limit, nested
|
||||||
|
t.Run("create YAML doc under limit, nested", func(t *testing.T) {
|
||||||
|
yamlBody := []byte(fmt.Sprintf(`
|
||||||
|
apiVersion: %s
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
values: `+strings.Repeat("[", 3*1024*1024/2-500)+strings.Repeat("]", 3*1024*1024/2-500), apiVersion, kind))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/yaml").
|
||||||
|
SetHeader("Content-Type", "application/yaml").
|
||||||
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
|
Body(yamlBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create YAML just under 3MB limit, not nested
|
||||||
|
t.Run("create YAML doc under limit, not nested", func(t *testing.T) {
|
||||||
|
yamlBody := []byte(fmt.Sprintf(`
|
||||||
|
apiVersion: %s
|
||||||
|
kind: %s
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
values: `+strings.Repeat("[", 3*1024*1024-1000), apiVersion, kind))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/yaml").
|
||||||
|
SetHeader("Content-Type", "application/yaml").
|
||||||
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
|
Body(yamlBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create JSON over 3MB limit
|
||||||
|
t.Run("create JSON over limit", func(t *testing.T) {
|
||||||
|
jsonBody := []byte(fmt.Sprintf(`{
|
||||||
|
"apiVersion": %q,
|
||||||
|
"kind": %q,
|
||||||
|
"metadata": {
|
||||||
|
"name": "test"
|
||||||
|
},
|
||||||
|
"values": `+strings.Repeat("[", 3*1024*1024/2)+strings.Repeat("]", 3*1024*1024/2)+"}", apiVersion, kind))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
|
Body(jsonBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsRequestEntityTooLargeError(err) {
|
||||||
|
t.Errorf("expected too large error, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create JSON just under 3MB limit, nested
|
||||||
|
t.Run("create JSON doc under limit, nested", func(t *testing.T) {
|
||||||
|
jsonBody := []byte(fmt.Sprintf(`{
|
||||||
|
"apiVersion": %q,
|
||||||
|
"kind": %q,
|
||||||
|
"metadata": {
|
||||||
|
"name": "test"
|
||||||
|
},
|
||||||
|
"values": `+strings.Repeat("[", 3*1024*1024/2-500)+strings.Repeat("]", 3*1024*1024/2-500)+"}", apiVersion, kind))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
|
Body(jsonBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create JSON just under 3MB limit, not nested
|
||||||
|
t.Run("create JSON doc under limit, not nested", func(t *testing.T) {
|
||||||
|
jsonBody := []byte(fmt.Sprintf(`{
|
||||||
|
"apiVersion": %q,
|
||||||
|
"kind": %q,
|
||||||
|
"metadata": {
|
||||||
|
"name": "test"
|
||||||
|
},
|
||||||
|
"values": `+strings.Repeat("[", 3*1024*1024-1000)+"}", apiVersion, kind))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
|
Body(jsonBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create instance to allow patching
|
||||||
|
{
|
||||||
|
jsonBody := []byte(fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "metadata": {"name": "test"}}`, apiVersion, kind))
|
||||||
|
_, err := rest.Post().AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).Body(jsonBody).DoRaw()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("JSONPatchType nested patch under limit", func(t *testing.T) {
|
||||||
|
patchBody := []byte(`[{"op":"add","path":"/foo","value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}]`)
|
||||||
|
err = rest.Patch(types.JSONPatchType).AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "test").
|
||||||
|
Body(patchBody).Do().Error()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected success or bad request err, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("MergePatchType nested patch under limit", func(t *testing.T) {
|
||||||
|
patchBody := []byte(`{"value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}`)
|
||||||
|
err = rest.Patch(types.MergePatchType).AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "test").
|
||||||
|
Body(patchBody).Do().Error()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected success or bad request err, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("ApplyPatchType nested patch under limit", func(t *testing.T) {
|
||||||
|
patchBody := []byte(`{"value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}`)
|
||||||
|
err = rest.Patch(types.ApplyPatchType).Param("fieldManager", "test").AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "test").
|
||||||
|
Body(patchBody).Do().Error()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request err, got %#v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -9,6 +9,7 @@ load(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"json_limit_test.go",
|
||||||
"json_test.go",
|
"json_test.go",
|
||||||
"meta_test.go",
|
"meta_test.go",
|
||||||
],
|
],
|
||||||
@ -17,6 +18,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -122,7 +122,27 @@ func (customNumberDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
|||||||
}
|
}
|
||||||
iter.ReportError("DecodeNumber", err.Error())
|
iter.ReportError("DecodeNumber", err.Error())
|
||||||
default:
|
default:
|
||||||
|
// init depth, if needed
|
||||||
|
if iter.Attachment == nil {
|
||||||
|
iter.Attachment = int(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember current depth
|
||||||
|
originalAttachment := iter.Attachment
|
||||||
|
|
||||||
|
// increment depth before descending
|
||||||
|
if i, ok := iter.Attachment.(int); ok {
|
||||||
|
iter.Attachment = i + 1
|
||||||
|
if i > 10000 {
|
||||||
|
iter.ReportError("parse", "exceeded max depth")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*(*interface{})(ptr) = iter.Read()
|
*(*interface{})(ptr) = iter.Read()
|
||||||
|
|
||||||
|
// restore current depth
|
||||||
|
iter.Attachment = originalAttachment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
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 json
|
||||||
|
|
||||||
|
import (
|
||||||
|
gojson "encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testcase struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
checkErr func(t testing.TB, err error)
|
||||||
|
|
||||||
|
benchmark bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func testcases() []testcase {
|
||||||
|
// verify we got an error of some kind
|
||||||
|
nonNilError := func(t testing.TB, err error) {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// verify the parse completed, either with success or a max depth error
|
||||||
|
successOrMaxDepthError := func(t testing.TB, err error) {
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "max depth") {
|
||||||
|
t.Errorf("expected success or error containing 'max depth', got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []testcase{
|
||||||
|
{
|
||||||
|
name: "3MB of deeply nested slices",
|
||||||
|
checkErr: successOrMaxDepthError,
|
||||||
|
data: []byte(`{"a":` + strings.Repeat(`[`, 3*1024*1024/2) + strings.Repeat(`]`, 3*1024*1024/2) + "}"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of unbalanced nested slices",
|
||||||
|
checkErr: nonNilError,
|
||||||
|
data: []byte(`{"a":` + strings.Repeat(`[`, 3*1024*1024)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of deeply nested maps",
|
||||||
|
checkErr: successOrMaxDepthError,
|
||||||
|
data: []byte(strings.Repeat(`{"":`, 3*1024*1024/5/2) + "{}" + strings.Repeat(`}`, 3*1024*1024/5/2)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of unbalanced nested maps",
|
||||||
|
checkErr: nonNilError,
|
||||||
|
data: []byte(strings.Repeat(`{"":`, 3*1024*1024/5)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of empty slices",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`[],`, 3*1024*1024/3-2) + `[]]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of slices",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`[0],`, 3*1024*1024/4-2) + `[0]]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of empty maps",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`{},`, 3*1024*1024/3-2) + `{}]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of maps",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`{"a":0},`, 3*1024*1024/8-2) + `{"a":0}]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of ints",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`0,`, 3*1024*1024/2-2) + `0]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of floats",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`0.0,`, 3*1024*1024/4-2) + `0.0]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of bools",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`true,`, 3*1024*1024/5-2) + `true]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of empty strings",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`"",`, 3*1024*1024/3-2) + `""]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of strings",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`"abcdefghijklmnopqrstuvwxyz012",`, 3*1024*1024/30-2) + `"abcdefghijklmnopqrstuvwxyz012"]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of nulls",
|
||||||
|
data: []byte(`{"a":[` + strings.Repeat(`null,`, 3*1024*1024/5-2) + `null]}`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoders = map[string]func([]byte, interface{}) error{
|
||||||
|
"gojson": gojson.Unmarshal,
|
||||||
|
"utiljson": utiljson.Unmarshal,
|
||||||
|
"jsoniter": CaseSensitiveJsonIterator().Unmarshal,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONLimits(t *testing.T) {
|
||||||
|
for _, tc := range testcases() {
|
||||||
|
if tc.benchmark {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
for decoderName, decoder := range decoders {
|
||||||
|
t.Run(decoderName, func(t *testing.T) {
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
err := decoder(tc.data, &v)
|
||||||
|
|
||||||
|
if tc.checkErr != nil {
|
||||||
|
tc.checkErr(t, err)
|
||||||
|
} else if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkJSONLimits(b *testing.B) {
|
||||||
|
for _, tc := range testcases() {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
for decoderName, decoder := range decoders {
|
||||||
|
b.Run(decoderName, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
err := decoder(tc.data, &v)
|
||||||
|
|
||||||
|
if tc.checkErr != nil {
|
||||||
|
tc.checkErr(b, err)
|
||||||
|
} else if err != nil {
|
||||||
|
b.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,6 @@
|
|||||||
package(default_visibility = ["//visibility:public"])
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
load(
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
@ -29,3 +26,14 @@ filegroup(
|
|||||||
srcs = [":package-srcs"],
|
srcs = [":package-srcs"],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["yaml_test.go"],
|
||||||
|
data = glob(["testdata/**"]),
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@ -0,0 +1,402 @@
|
|||||||
|
/*
|
||||||
|
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 yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
sigsyaml "sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testcase struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
error string
|
||||||
|
|
||||||
|
benchmark bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func testcases() []testcase {
|
||||||
|
return []testcase{
|
||||||
|
{
|
||||||
|
name: "arrays of string aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a ["webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb"]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrays of empty string aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a ["","","","","","","","",""]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrays of null aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a [null,null,null,null,null,null,null,null,null]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrays of zero int aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a [0,0,0,0,0,0,0,0,0]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrays of zero float aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrays of big float aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a [1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrays of bool aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a [true,true,true,true,true,true,true,true,true]
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map key aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a {"verylongkey1":"","verylongkey2":"","verylongkey3":"","verylongkey4":"","verylongkey5":"","verylongkey6":"","verylongkey7":"","verylongkey8":"","verylongkey9":""}
|
||||||
|
b: &b {"verylongkey1":*a,"verylongkey2":*a,"verylongkey3":*a,"verylongkey4":*a,"verylongkey5":*a,"verylongkey6":*a,"verylongkey7":*a,"verylongkey8":*a,"verylongkey9":*a}
|
||||||
|
c: &c {"verylongkey1":*b,"verylongkey2":*b,"verylongkey3":*b,"verylongkey4":*b,"verylongkey5":*b,"verylongkey6":*b,"verylongkey7":*b,"verylongkey8":*b,"verylongkey9":*b}
|
||||||
|
d: &d {"verylongkey1":*c,"verylongkey2":*c,"verylongkey3":*c,"verylongkey4":*c,"verylongkey5":*c,"verylongkey6":*c,"verylongkey7":*c,"verylongkey8":*c,"verylongkey9":*c}
|
||||||
|
e: &e {"verylongkey1":*d,"verylongkey2":*d,"verylongkey3":*d,"verylongkey4":*d,"verylongkey5":*d,"verylongkey6":*d,"verylongkey7":*d,"verylongkey8":*d,"verylongkey9":*d}
|
||||||
|
f: &f {"verylongkey1":*e,"verylongkey2":*e,"verylongkey3":*e,"verylongkey4":*e,"verylongkey5":*e,"verylongkey6":*e,"verylongkey7":*e,"verylongkey8":*e,"verylongkey9":*e}
|
||||||
|
g: &g {"verylongkey1":*f,"verylongkey2":*f,"verylongkey3":*f,"verylongkey4":*f,"verylongkey5":*f,"verylongkey6":*f,"verylongkey7":*f,"verylongkey8":*f,"verylongkey9":*f}
|
||||||
|
h: &h {"verylongkey1":*g,"verylongkey2":*g,"verylongkey3":*g,"verylongkey4":*g,"verylongkey5":*g,"verylongkey6":*g,"verylongkey7":*g,"verylongkey8":*g,"verylongkey9":*g}
|
||||||
|
i: &i {"verylongkey1":*h,"verylongkey2":*h,"verylongkey3":*h,"verylongkey4":*h,"verylongkey5":*h,"verylongkey6":*h,"verylongkey7":*h,"verylongkey8":*h,"verylongkey9":*h}
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map value aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a {"1":"verylongmapvalue","2":"verylongmapvalue","3":"verylongmapvalue","4":"verylongmapvalue","5":"verylongmapvalue","6":"verylongmapvalue","7":"verylongmapvalue","8":"verylongmapvalue","9":"verylongmapvalue"}
|
||||||
|
b: &b {"1":*a,"2":*a,"3":*a,"4":*a,"5":*a,"6":*a,"7":*a,"8":*a,"9":*a}
|
||||||
|
c: &c {"1":*b,"2":*b,"3":*b,"4":*b,"5":*b,"6":*b,"7":*b,"8":*b,"9":*b}
|
||||||
|
d: &d {"1":*c,"2":*c,"3":*c,"4":*c,"5":*c,"6":*c,"7":*c,"8":*c,"9":*c}
|
||||||
|
e: &e {"1":*d,"2":*d,"3":*d,"4":*d,"5":*d,"6":*d,"7":*d,"8":*d,"9":*d}
|
||||||
|
f: &f {"1":*e,"2":*e,"3":*e,"4":*e,"5":*e,"6":*e,"7":*e,"8":*e,"9":*e}
|
||||||
|
g: &g {"1":*f,"2":*f,"3":*f,"4":*f,"5":*f,"6":*f,"7":*f,"8":*f,"9":*f}
|
||||||
|
h: &h {"1":*g,"2":*g,"3":*g,"4":*g,"5":*g,"6":*g,"7":*g,"8":*g,"9":*g}
|
||||||
|
i: &i {"1":*h,"2":*h,"3":*h,"4":*h,"5":*h,"6":*h,"7":*h,"8":*h,"9":*h}
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested map aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a {"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
b: &b {"1":*a,"2":*a,"3":*a,"4":*a,"5":*a,"6":*a,"7":*a,"8":*a,"9":*a}
|
||||||
|
c: &c {"1":*b,"2":*b,"3":*b,"4":*b,"5":*b,"6":*b,"7":*b,"8":*b,"9":*b}
|
||||||
|
d: &d {"1":*c,"2":*c,"3":*c,"4":*c,"5":*c,"6":*c,"7":*c,"8":*c,"9":*c}
|
||||||
|
e: &e {"1":*d,"2":*d,"3":*d,"4":*d,"5":*d,"6":*d,"7":*d,"8":*d,"9":*d}
|
||||||
|
f: &f {"1":*e,"2":*e,"3":*e,"4":*e,"5":*e,"6":*e,"7":*e,"8":*e,"9":*e}
|
||||||
|
g: &g {"1":*f,"2":*f,"3":*f,"4":*f,"5":*f,"6":*f,"7":*f,"8":*f,"9":*f}
|
||||||
|
h: &h {"1":*g,"2":*g,"3":*g,"4":*g,"5":*g,"6":*g,"7":*g,"8":*g,"9":*g}
|
||||||
|
i: &i {"1":*h,"2":*h,"3":*h,"4":*h,"5":*h,"6":*h,"7":*h,"8":*h,"9":*h}
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested slice aliases",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
a: &a [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[""]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
|
||||||
|
b: &b [[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]]]
|
||||||
|
c: &c [[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]]]
|
||||||
|
d: &d [[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]]]
|
||||||
|
e: &e [[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]]]
|
||||||
|
f: &f [[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]]]
|
||||||
|
g: &g [[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]]]
|
||||||
|
h: &h [[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]]]
|
||||||
|
i: &i [[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]]]
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: yaml-bomb
|
||||||
|
namespace: default
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB map without alias",
|
||||||
|
data: []byte(`a: &a [{a}` + strings.Repeat(`,{a}`, 3*1024*1024/4) + `]`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB map with alias",
|
||||||
|
error: "excessive aliasing",
|
||||||
|
data: []byte(`
|
||||||
|
a: &a [{a}` + strings.Repeat(`,{a}`, 3*1024*1024/4) + `]
|
||||||
|
b: &b [*a]`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deeply nested slices",
|
||||||
|
error: "max depth",
|
||||||
|
data: []byte(strings.Repeat(`[`, 3*1024*1024)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deeply nested maps",
|
||||||
|
error: "max depth",
|
||||||
|
data: []byte("x: " + strings.Repeat(`{`, 3*1024*1024)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deeply nested indents",
|
||||||
|
error: "max depth",
|
||||||
|
data: []byte(strings.Repeat(`- `, 3*1024*1024)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of 1000-indent lines",
|
||||||
|
data: []byte(strings.Repeat(strings.Repeat(`- `, 1000)+"\n", 3*1024/2)),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of empty slices",
|
||||||
|
data: []byte(`[` + strings.Repeat(`[],`, 3*1024*1024/3-2) + `[]]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of slices",
|
||||||
|
data: []byte(`[` + strings.Repeat(`[0],`, 3*1024*1024/4-2) + `[0]]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of empty maps",
|
||||||
|
data: []byte(`[` + strings.Repeat(`{},`, 3*1024*1024/3-2) + `{}]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of maps",
|
||||||
|
data: []byte(`[` + strings.Repeat(`{a},`, 3*1024*1024/4-2) + `{a}]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of ints",
|
||||||
|
data: []byte(`[` + strings.Repeat(`0,`, 3*1024*1024/2-2) + `0]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of floats",
|
||||||
|
data: []byte(`[` + strings.Repeat(`0.0,`, 3*1024*1024/4-2) + `0.0]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of bools",
|
||||||
|
data: []byte(`[` + strings.Repeat(`true,`, 3*1024*1024/5-2) + `true]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of empty strings",
|
||||||
|
data: []byte(`[` + strings.Repeat(`"",`, 3*1024*1024/3-2) + `""]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of strings",
|
||||||
|
data: []byte(`[` + strings.Repeat(`"abcdefghijklmnopqrstuvwxyz012",`, 3*1024*1024/30-2) + `"abcdefghijklmnopqrstuvwxyz012"]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "3MB of nulls",
|
||||||
|
data: []byte(`[` + strings.Repeat(`null,`, 3*1024*1024/5-2) + `null]`),
|
||||||
|
benchmark: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoders = map[string]func([]byte) ([]byte, error){
|
||||||
|
"sigsyaml": sigsyaml.YAMLToJSON,
|
||||||
|
"utilyaml": yaml.ToJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLLimits(t *testing.T) {
|
||||||
|
for _, tc := range testcases() {
|
||||||
|
if tc.benchmark {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
for decoderName, decoder := range decoders {
|
||||||
|
t.Run(decoderName, func(t *testing.T) {
|
||||||
|
_, err := decoder(tc.data)
|
||||||
|
if len(tc.error) == 0 {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tc.error) {
|
||||||
|
t.Errorf("expected %q error, got %v", tc.error, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkYAMLLimits(b *testing.B) {
|
||||||
|
for _, tc := range testcases() {
|
||||||
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
|
for decoderName, decoder := range decoders {
|
||||||
|
b.Run(decoderName, func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := decoder(tc.data)
|
||||||
|
if len(tc.error) == 0 {
|
||||||
|
if err != nil {
|
||||||
|
b.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tc.error) {
|
||||||
|
b.Errorf("expected %q error, got %v", tc.error, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ package json
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,6 +35,9 @@ func Marshal(v interface{}) ([]byte, error) {
|
|||||||
return json.Marshal(v)
|
return json.Marshal(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// limit recursive depth to prevent stack overflow errors
|
||||||
|
const maxDepth = 10000
|
||||||
|
|
||||||
// Unmarshal unmarshals the given data
|
// Unmarshal unmarshals the given data
|
||||||
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
|
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
|
||||||
func Unmarshal(data []byte, v interface{}) error {
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
@ -48,7 +52,7 @@ func Unmarshal(data []byte, v interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
|
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
|
||||||
return convertMapNumbers(*v)
|
return convertMapNumbers(*v, 0)
|
||||||
|
|
||||||
case *[]interface{}:
|
case *[]interface{}:
|
||||||
// Build a decoder from the given data
|
// Build a decoder from the given data
|
||||||
@ -60,7 +64,7 @@ func Unmarshal(data []byte, v interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
|
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
|
||||||
return convertSliceNumbers(*v)
|
return convertSliceNumbers(*v, 0)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return json.Unmarshal(data, v)
|
return json.Unmarshal(data, v)
|
||||||
@ -69,16 +73,20 @@ func Unmarshal(data []byte, v interface{}) error {
|
|||||||
|
|
||||||
// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
|
// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
|
||||||
// values which are map[string]interface{} or []interface{} are recursively visited
|
// values which are map[string]interface{} or []interface{} are recursively visited
|
||||||
func convertMapNumbers(m map[string]interface{}) error {
|
func convertMapNumbers(m map[string]interface{}, depth int) error {
|
||||||
|
if depth > maxDepth {
|
||||||
|
return fmt.Errorf("exceeded max depth of %d", maxDepth)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case json.Number:
|
case json.Number:
|
||||||
m[k], err = convertNumber(v)
|
m[k], err = convertNumber(v)
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
err = convertMapNumbers(v)
|
err = convertMapNumbers(v, depth+1)
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
err = convertSliceNumbers(v)
|
err = convertSliceNumbers(v, depth+1)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -89,16 +97,20 @@ func convertMapNumbers(m map[string]interface{}) error {
|
|||||||
|
|
||||||
// convertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
|
// convertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
|
||||||
// values which are map[string]interface{} or []interface{} are recursively visited
|
// values which are map[string]interface{} or []interface{} are recursively visited
|
||||||
func convertSliceNumbers(s []interface{}) error {
|
func convertSliceNumbers(s []interface{}, depth int) error {
|
||||||
|
if depth > maxDepth {
|
||||||
|
return fmt.Errorf("exceeded max depth of %d", maxDepth)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for i, v := range s {
|
for i, v := range s {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case json.Number:
|
case json.Number:
|
||||||
s[i], err = convertNumber(v)
|
s[i], err = convertNumber(v)
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
err = convertMapNumbers(v)
|
err = convertMapNumbers(v, depth+1)
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
err = convertSliceNumbers(v)
|
err = convertSliceNumbers(v, depth+1)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -195,11 +195,11 @@ func (f *fieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager
|
|||||||
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
|
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
|
||||||
return nil, fmt.Errorf("error decoding YAML: %v", err)
|
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if patchObj.GetManagedFields() != nil {
|
if patchObj.GetManagedFields() != nil {
|
||||||
return nil, fmt.Errorf("managed fields must be nil but was %v", patchObj.GetManagedFields())
|
return nil, errors.NewBadRequest(fmt.Sprintf("metadata.managedFields must be nil"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if patchObj.GetAPIVersion() != f.groupVersion.String() {
|
if patchObj.GetAPIVersion() != f.groupVersion.String() {
|
||||||
|
@ -337,6 +337,15 @@ func (p *jsonPatcher) createNewObject() (runtime.Object, error) {
|
|||||||
func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
|
func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
|
||||||
switch p.patchType {
|
switch p.patchType {
|
||||||
case types.JSONPatchType:
|
case types.JSONPatchType:
|
||||||
|
// sanity check potentially abusive patches
|
||||||
|
// TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789)
|
||||||
|
if len(p.patchBytes) > 1024*1024 {
|
||||||
|
v := []interface{}{}
|
||||||
|
if err := json.Unmarshal(p.patchBytes, v); err != nil {
|
||||||
|
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
patchObj, err := jsonpatch.DecodePatch(p.patchBytes)
|
patchObj, err := jsonpatch.DecodePatch(p.patchBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.NewBadRequest(err.Error())
|
return nil, errors.NewBadRequest(err.Error())
|
||||||
@ -352,6 +361,15 @@ func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr
|
|||||||
}
|
}
|
||||||
return patchedJS, nil
|
return patchedJS, nil
|
||||||
case types.MergePatchType:
|
case types.MergePatchType:
|
||||||
|
// sanity check potentially abusive patches
|
||||||
|
// TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789)
|
||||||
|
if len(p.patchBytes) > 1024*1024 {
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal(p.patchBytes, v); err != nil {
|
||||||
|
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return jsonpatch.MergePatch(versionedJS, p.patchBytes)
|
return jsonpatch.MergePatch(versionedJS, p.patchBytes)
|
||||||
default:
|
default:
|
||||||
// only here as a safety net - go-restful filters content-type
|
// only here as a safety net - go-restful filters content-type
|
||||||
|
@ -180,7 +180,7 @@ type Config struct {
|
|||||||
// patch may cause.
|
// patch may cause.
|
||||||
// This affects all places that applies json patch in the binary.
|
// This affects all places that applies json patch in the binary.
|
||||||
JSONPatchMaxCopyBytes int64
|
JSONPatchMaxCopyBytes int64
|
||||||
// The limit on the request body size that would be accepted and decoded in a write request.
|
// The limit on the request size that would be accepted and decoded in a write request
|
||||||
// 0 means no limit.
|
// 0 means no limit.
|
||||||
MaxRequestBodyBytes int64
|
MaxRequestBodyBytes int64
|
||||||
// MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further
|
// MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further
|
||||||
@ -295,22 +295,20 @@ func NewConfig(codecs serializer.CodecFactory) *Config {
|
|||||||
MinRequestTimeout: 1800,
|
MinRequestTimeout: 1800,
|
||||||
LivezGracePeriod: time.Duration(0),
|
LivezGracePeriod: time.Duration(0),
|
||||||
ShutdownDelayDuration: time.Duration(0),
|
ShutdownDelayDuration: time.Duration(0),
|
||||||
// 10MB is the recommended maximum client request size in bytes
|
// 1.5MB is the default client request size in bytes
|
||||||
// the etcd server should accept. See
|
// the etcd server should accept. See
|
||||||
// https://github.com/etcd-io/etcd/blob/release-3.3/etcdserver/server.go#L90.
|
// https://github.com/etcd-io/etcd/blob/release-3.4/embed/config.go#L56.
|
||||||
// A request body might be encoded in json, and is converted to
|
// A request body might be encoded in json, and is converted to
|
||||||
// proto when persisted in etcd. Assuming the upper bound of
|
// proto when persisted in etcd, so we allow 2x as the largest size
|
||||||
// the size ratio is 10:1, we set 100MB as the largest size
|
|
||||||
// increase the "copy" operations in a json patch may cause.
|
// increase the "copy" operations in a json patch may cause.
|
||||||
JSONPatchMaxCopyBytes: int64(100 * 1024 * 1024),
|
JSONPatchMaxCopyBytes: int64(3 * 1024 * 1024),
|
||||||
// 10MB is the recommended maximum client request size in bytes
|
// 1.5MB is the recommended client request size in byte
|
||||||
// the etcd server should accept. See
|
// the etcd server should accept. See
|
||||||
// https://github.com/etcd-io/etcd/blob/release-3.3/etcdserver/server.go#L90.
|
// https://github.com/etcd-io/etcd/blob/release-3.4/embed/config.go#L56.
|
||||||
// A request body might be encoded in json, and is converted to
|
// A request body might be encoded in json, and is converted to
|
||||||
// proto when persisted in etcd. Assuming the upper bound of
|
// proto when persisted in etcd, so we allow 2x as the largest request
|
||||||
// the size ratio is 10:1, we set 100MB as the largest request
|
|
||||||
// body size to be accepted and decoded in a write request.
|
// body size to be accepted and decoded in a write request.
|
||||||
MaxRequestBodyBytes: int64(100 * 1024 * 1024),
|
MaxRequestBodyBytes: int64(3 * 1024 * 1024),
|
||||||
|
|
||||||
// Default to treating watch as a long-running operation
|
// Default to treating watch as a long-running operation
|
||||||
// Generic API servers have no inherent long-running subresources
|
// Generic API servers have no inherent long-running subresources
|
||||||
|
@ -21,11 +21,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
|
||||||
"k8s.io/kubernetes/test/integration/framework"
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,13 +33,11 @@ import (
|
|||||||
func TestMaxResourceSize(t *testing.T) {
|
func TestMaxResourceSize(t *testing.T) {
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
defer close(stopCh)
|
defer close(stopCh)
|
||||||
clientSet, _ := framework.StartTestServer(t, stopCh, framework.TestServerSetup{
|
clientSet, _ := framework.StartTestServer(t, stopCh, framework.TestServerSetup{})
|
||||||
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
|
|
||||||
opts.GenericServerRunOptions.MaxRequestBodyBytes = 1024 * 1024
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
hugeData := []byte(strings.Repeat("x", 1024*1024+1))
|
hugeData := []byte(strings.Repeat("x", 3*1024*1024+1))
|
||||||
|
|
||||||
|
rest := clientSet.Discovery().RESTClient()
|
||||||
|
|
||||||
c := clientSet.CoreV1().RESTClient()
|
c := clientSet.CoreV1().RESTClient()
|
||||||
t.Run("Create should limit the request body size", func(t *testing.T) {
|
t.Run("Create should limit the request body size", func(t *testing.T) {
|
||||||
@ -87,6 +85,38 @@ func TestMaxResourceSize(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Run("JSONPatchType should handle a patch just under the max limit", func(t *testing.T) {
|
||||||
|
patchBody := []byte(`[{"op":"add","path":"/foo","value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}]`)
|
||||||
|
err = rest.Patch(types.JSONPatchType).AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")).
|
||||||
|
Body(patchBody).Do().Error()
|
||||||
|
if err != nil && !errors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected success or bad request err, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("MergePatchType should handle a patch just under the max limit", func(t *testing.T) {
|
||||||
|
patchBody := []byte(`{"value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}`)
|
||||||
|
err = rest.Patch(types.MergePatchType).AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")).
|
||||||
|
Body(patchBody).Do().Error()
|
||||||
|
if err != nil && !errors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected success or bad request err, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("StrategicMergePatchType should handle a patch just under the max limit", func(t *testing.T) {
|
||||||
|
patchBody := []byte(`{"value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}`)
|
||||||
|
err = rest.Patch(types.StrategicMergePatchType).AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")).
|
||||||
|
Body(patchBody).Do().Error()
|
||||||
|
if err != nil && !errors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected success or bad request err, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("ApplyPatchType should handle a patch just under the max limit", func(t *testing.T) {
|
||||||
|
patchBody := []byte(`{"value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}`)
|
||||||
|
err = rest.Patch(types.ApplyPatchType).Param("fieldManager", "test").AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")).
|
||||||
|
Body(patchBody).Do().Error()
|
||||||
|
if err != nil && !errors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected success or bad request err, got %#v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
t.Run("Delete should limit the request body size", func(t *testing.T) {
|
t.Run("Delete should limit the request body size", func(t *testing.T) {
|
||||||
err = c.Delete().AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")).
|
err = c.Delete().AbsPath(fmt.Sprintf("/api/v1/namespaces/default/secrets/test")).
|
||||||
Body(hugeData).Do().Error()
|
Body(hugeData).Do().Error()
|
||||||
@ -98,4 +128,128 @@ func TestMaxResourceSize(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create YAML over 3MB limit
|
||||||
|
t.Run("create should limit yaml parsing", func(t *testing.T) {
|
||||||
|
yamlBody := []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: mytest
|
||||||
|
values: ` + strings.Repeat("[", 3*1024*1024))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/yaml").
|
||||||
|
SetHeader("Content-Type", "application/yaml").
|
||||||
|
AbsPath("/api/v1/namespaces/default/configmaps").
|
||||||
|
Body(yamlBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsRequestEntityTooLargeError(err) {
|
||||||
|
t.Errorf("expected too large error, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create YAML just under 3MB limit, nested
|
||||||
|
t.Run("create should handle a yaml document just under the maximum size with correct nesting", func(t *testing.T) {
|
||||||
|
yamlBody := []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: mytest
|
||||||
|
values: ` + strings.Repeat("[", 3*1024*1024/2-500) + strings.Repeat("]", 3*1024*1024/2-500))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/yaml").
|
||||||
|
SetHeader("Content-Type", "application/yaml").
|
||||||
|
AbsPath("/api/v1/namespaces/default/configmaps").
|
||||||
|
Body(yamlBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create YAML just under 3MB limit, not nested
|
||||||
|
t.Run("create should handle a yaml document just under the maximum size with unbalanced nesting", func(t *testing.T) {
|
||||||
|
yamlBody := []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: mytest
|
||||||
|
values: ` + strings.Repeat("[", 3*1024*1024-1000))
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/yaml").
|
||||||
|
SetHeader("Content-Type", "application/yaml").
|
||||||
|
AbsPath("/api/v1/namespaces/default/configmaps").
|
||||||
|
Body(yamlBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create JSON over 3MB limit
|
||||||
|
t.Run("create should limit json parsing", func(t *testing.T) {
|
||||||
|
jsonBody := []byte(`{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "mytest"
|
||||||
|
},
|
||||||
|
"values": ` + strings.Repeat("[", 3*1024*1024/2) + strings.Repeat("]", 3*1024*1024/2) + "}")
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
AbsPath("/api/v1/namespaces/default/configmaps").
|
||||||
|
Body(jsonBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsRequestEntityTooLargeError(err) {
|
||||||
|
t.Errorf("expected too large error, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create JSON just under 3MB limit, nested
|
||||||
|
t.Run("create should handle a json document just under the maximum size with correct nesting", func(t *testing.T) {
|
||||||
|
jsonBody := []byte(`{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "mytest"
|
||||||
|
},
|
||||||
|
"values": ` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + "}")
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
AbsPath("/api/v1/namespaces/default/configmaps").
|
||||||
|
Body(jsonBody).
|
||||||
|
DoRaw()
|
||||||
|
// TODO(liggitt): expect bad request on deep nesting, rather than success on dropped unknown field data
|
||||||
|
if err != nil && !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create JSON just under 3MB limit, not nested
|
||||||
|
t.Run("create should handle a json document just under the maximum size with unbalanced nesting", func(t *testing.T) {
|
||||||
|
jsonBody := []byte(`{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "mytest"
|
||||||
|
},
|
||||||
|
"values": ` + strings.Repeat("[", 3*1024*1024-1000) + "}")
|
||||||
|
|
||||||
|
_, err := rest.Post().
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
AbsPath("/api/v1/namespaces/default/configmaps").
|
||||||
|
Body(jsonBody).
|
||||||
|
DoRaw()
|
||||||
|
if !apierrors.IsBadRequest(err) {
|
||||||
|
t.Errorf("expected bad request, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -307,30 +307,36 @@ func TestObjectSizeResponses(t *testing.T) {
|
|||||||
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL})
|
client := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL})
|
||||||
|
|
||||||
const DeploymentMegabyteSize = 100000
|
const DeploymentMegabyteSize = 100000
|
||||||
const DeploymentTwoMegabyteSize = 1000000
|
const DeploymentTwoMegabyteSize = 175000
|
||||||
|
const DeploymentThreeMegabyteSize = 250000
|
||||||
|
|
||||||
expectedMsgFor1MB := `etcdserver: request is too large`
|
expectedMsgFor1MB := `etcdserver: request is too large`
|
||||||
expectedMsgFor2MB := `rpc error: code = ResourceExhausted desc = trying to send message larger than max`
|
expectedMsgFor2MB := `rpc error: code = ResourceExhausted desc = trying to send message larger than max`
|
||||||
|
expectedMsgFor3MB := `Request entity too large: limit is 3145728`
|
||||||
expectedMsgForLargeAnnotation := `metadata.annotations: Too long: must have at most 262144 characters`
|
expectedMsgForLargeAnnotation := `metadata.annotations: Too long: must have at most 262144 characters`
|
||||||
|
|
||||||
deployment1 := constructBody("a", DeploymentMegabyteSize, "labels", t) // >1 MB file
|
deployment1 := constructBody("a", DeploymentMegabyteSize, "labels", t) // >1 MB file
|
||||||
deployment2 := constructBody("a", DeploymentTwoMegabyteSize, "labels", t) // >2 MB file
|
deployment2 := constructBody("a", DeploymentTwoMegabyteSize, "labels", t) // >2 MB file
|
||||||
|
deployment3 := constructBody("a", DeploymentThreeMegabyteSize, "labels", t) // >3 MB file
|
||||||
|
|
||||||
deployment3 := constructBody("a", DeploymentMegabyteSize, "annotations", t)
|
deployment4 := constructBody("a", DeploymentMegabyteSize, "annotations", t)
|
||||||
|
|
||||||
deployment4 := constructBody("sample/sample", DeploymentMegabyteSize, "finalizers", t) // >1 MB file
|
deployment5 := constructBody("sample/sample", DeploymentMegabyteSize, "finalizers", t) // >1 MB file
|
||||||
deployment5 := constructBody("sample/sample", DeploymentTwoMegabyteSize, "finalizers", t) // >2 MB file
|
deployment6 := constructBody("sample/sample", DeploymentTwoMegabyteSize, "finalizers", t) // >2 MB file
|
||||||
|
deployment7 := constructBody("sample/sample", DeploymentThreeMegabyteSize, "finalizers", t) // >3 MB file
|
||||||
|
|
||||||
requests := []struct {
|
requests := []struct {
|
||||||
size string
|
size string
|
||||||
deploymentObject *appsv1.Deployment
|
deploymentObject *appsv1.Deployment
|
||||||
expectedMessage string
|
expectedMessage string
|
||||||
}{
|
}{
|
||||||
{"1 MB", deployment1, expectedMsgFor1MB},
|
{"1 MB labels", deployment1, expectedMsgFor1MB},
|
||||||
{"2 MB", deployment2, expectedMsgFor2MB},
|
{"2 MB labels", deployment2, expectedMsgFor2MB},
|
||||||
{"1 MB", deployment3, expectedMsgForLargeAnnotation},
|
{"3 MB labels", deployment3, expectedMsgFor3MB},
|
||||||
{"1 MB", deployment4, expectedMsgFor1MB},
|
{"1 MB annotations", deployment4, expectedMsgForLargeAnnotation},
|
||||||
{"2 MB", deployment5, expectedMsgFor2MB},
|
{"1 MB finalizers", deployment5, expectedMsgFor1MB},
|
||||||
|
{"2 MB finalizers", deployment6, expectedMsgFor2MB},
|
||||||
|
{"3 MB finalizers", deployment7, expectedMsgFor3MB},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range requests {
|
for _, r := range requests {
|
||||||
|
Loading…
Reference in New Issue
Block a user