Configure JSON content type for generic webhook RESTClient.
Authorization, token authentication, imagepolicy admission, and audit webhooks configure RESTClients that encode to JSON regardless of the ContentType of the provided rest.Config. Because this is opaque to the RESTClient, configuring a ContentType other than "application/json" results in requests with JSON-encoded bodies and a non-JSON media type in the Content-Type header. Webhook servers that respect the Content-Type request header will be unable to decode an object from the request body. Explicitly overriding the ContentType of the provided rest.Config fixes this issue and is consistent with how clients are constructed for conversion and admission webhooks. Kubernetes-commit: ed07efbc57939afdf154afa80be35507d0a81d66
This commit is contained in:
parent
234cda4ae8
commit
36d7848a30
|
@ -83,6 +83,7 @@ func NewGenericWebhook(scheme *runtime.Scheme, codecFactory serializer.CodecFact
|
||||||
clientConfig := rest.CopyConfig(config)
|
clientConfig := rest.CopyConfig(config)
|
||||||
|
|
||||||
codec := codecFactory.LegacyCodec(groupVersions...)
|
codec := codecFactory.LegacyCodec(groupVersions...)
|
||||||
|
clientConfig.ContentType = runtime.ContentTypeJSON
|
||||||
clientConfig.ContentConfig.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
|
clientConfig.ContentConfig.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
|
||||||
|
|
||||||
clientConfig.Wrap(x509metrics.NewDeprecatedCertificateRoundTripperWrapperConstructor(
|
clientConfig.Wrap(x509metrics.NewDeprecatedCertificateRoundTripperWrapperConstructor(
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -33,11 +34,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
exampleinstall "k8s.io/apiserver/pkg/apis/example/install"
|
||||||
|
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||||
|
@ -927,3 +932,57 @@ func getSingleCounterValueFromRegistry(t *testing.T, r metrics.Gatherer, name st
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRESTConfigContentType(t *testing.T) {
|
||||||
|
server, err := newTestServer(clientCert, clientKey, caCert, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if got := r.Header.Get("Content-Type"); got != runtime.ContentTypeJSON {
|
||||||
|
t.Errorf("expected request content-type: want %q got %q", runtime.ContentTypeJSON, got)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to read request body: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, new(any)); err != nil {
|
||||||
|
switch {
|
||||||
|
case len(body) == 0:
|
||||||
|
t.Log("empty request body")
|
||||||
|
case utf8.Valid(body):
|
||||||
|
t.Logf("request body: %s", string(body))
|
||||||
|
default:
|
||||||
|
t.Logf("request body: 0x%x", body)
|
||||||
|
}
|
||||||
|
t.Errorf("failed to unmarshal request body as json: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create server: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
config := &rest.Config{
|
||||||
|
ContentConfig: rest.ContentConfig{
|
||||||
|
ContentType: "foo/bar",
|
||||||
|
},
|
||||||
|
Host: server.URL,
|
||||||
|
TLSClientConfig: rest.TLSClientConfig{
|
||||||
|
CAData: caCert,
|
||||||
|
CertData: clientCert,
|
||||||
|
KeyData: clientKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
exampleinstall.Install(scheme)
|
||||||
|
codecs := serializer.NewCodecFactory(scheme)
|
||||||
|
groupVersions := []schema.GroupVersion{examplev1.SchemeGroupVersion}
|
||||||
|
wh, err := NewGenericWebhook(scheme, codecs, config, groupVersions, retryBackoff)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create the webhook: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wh.RestClient.Post().Body(&examplev1.Pod{}).Do(context.TODO()).Error(); err != nil {
|
||||||
|
t.Fatalf("failed to complete request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue