Add integration test for rejecting non-JSON RawExtensions.

The content of a RawExtension isn't constrained on its own, but there is a de facto requirement that
the object in a request body can be converted to unstructured. Currently, a RawExtension can only be
converted to unstructured if its Raw field is nil or if it contains valid JSON.
This commit is contained in:
Ben Luddy 2024-07-03 14:51:16 -04:00
parent ae66cfe582
commit d4e146c861
No known key found for this signature in database
GPG Key ID: A6551E73A5974C30

View File

@ -0,0 +1,108 @@
/*
Copyright 2024 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 (
"context"
"encoding/json"
"errors"
"fmt"
"testing"
"golang.org/x/net/http2"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
appsv1applyconfigurations "k8s.io/client-go/applyconfigurations/apps/v1"
clientset "k8s.io/client-go/kubernetes"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration/framework"
)
// TestRequestObjectConvertibleToUnstructured tests that write requests fail if the request object
// is not convertible to unstructured. The ability to convert an object to unstructured ensures that
// it can be encoded as JSON and that field managers can be determined.
func TestRequestObjectConvertibleToUnstructured(t *testing.T) {
server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{}, framework.SharedEtcd())
defer server.TearDownFn()
for i, raw := range []string{
``,
`"`,
`{`,
`[`,
`1z`,
`z`,
} {
// The Protobuf request encoding is required. Invalid JSON cannot be embedded in a
// JSON object or array without making the containing object or array also invalid.
protoConfig := server.ClientConfig
protoConfig.ContentConfig.ContentType = runtime.ContentTypeProtobuf
protoConfig.ContentConfig.AcceptContentTypes = runtime.ContentTypeProtobuf
protoClient, err := clientset.NewForConfig(protoConfig)
if err != nil {
t.Fatalf("unexpected error creating proto client: %v", err)
}
createError := new(http2.StreamError)
if _, err := protoClient.AppsV1().ControllerRevisions("default").Create(context.TODO(), &appsv1.ControllerRevision{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("test-revision-create-%d", i),
},
Data: runtime.RawExtension{Raw: []byte(raw)},
}, metav1.CreateOptions{}); errors.As(err, createError) && createError.Code == http2.ErrCodeInternal {
t.Logf("create returned internal error as expected with rawextension %#v: %v", raw, err)
} else {
t.Errorf("create returned unexpected error: %#v", err)
}
var marshalerError *json.MarshalerError
if _, err := protoClient.AppsV1().ControllerRevisions("default").Apply(context.TODO(), appsv1applyconfigurations.ControllerRevision("test-revision-apply", "default").
WithData(runtime.RawExtension{Raw: []byte(raw)}),
metav1.ApplyOptions{}); errors.As(err, &marshalerError) {
// In this case the error is currently client-side, since apply request
// bodies must be encoded as JSON. Included here to cover the future
// possibility of Protobuf-encoded apply configurations.
t.Logf("apply returned client-side marshaler error as expected with rawextension %#v: %v", raw, err)
} else {
t.Errorf("apply returned unexpected error: %#v", err)
}
// Create an object to be updated. If the object does not exist, then the update
// will short-circuit on "not found" before it encounters the error that is
// interesting to this test.
existing, err := protoClient.AppsV1().ControllerRevisions("default").Create(context.TODO(), &appsv1.ControllerRevision{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("test-revision-update-%d", i),
},
Data: runtime.RawExtension{Raw: []byte(`{}`)},
}, metav1.CreateOptions{})
if err != nil {
t.Errorf("expected nil create error, got: %v", err)
continue
}
updateError := new(http2.StreamError)
existing.Data = runtime.RawExtension{Raw: []byte(raw)}
if _, err := protoClient.AppsV1().ControllerRevisions(existing.Namespace).Update(context.TODO(), existing, metav1.UpdateOptions{}); errors.As(err, updateError) && updateError.Code == http2.ErrCodeInternal {
t.Logf("update returned internal error as expected with rawextension %#v: %v", raw, err)
} else {
t.Errorf("update returned unexpected error: %#v", err)
}
}
}