k8s.io/apiserver@v0.31.1/plugin/pkg/audit/webhook/webhook_test.go (about) 1 /* 2 Copyright 2017 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 webhook 18 19 import ( 20 stdjson "encoding/json" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "net/http" 25 "net/http/httptest" 26 "os" 27 "reflect" 28 "testing" 29 "time" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/apimachinery/pkg/runtime/serializer/json" 37 "k8s.io/apimachinery/pkg/util/wait" 38 auditinternal "k8s.io/apiserver/pkg/apis/audit" 39 auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" 40 "k8s.io/apiserver/pkg/audit" 41 "k8s.io/client-go/tools/clientcmd/api/v1" 42 ) 43 44 // newWebhookHandler returns a handler which receives webhook events and decodes the 45 // request body. The caller passes a callback which is called on each webhook POST. 46 // The object passed to cb is of the same type as list. 47 func newWebhookHandler(t *testing.T, list runtime.Object, cb func(events runtime.Object)) http.Handler { 48 s := json.NewSerializer(json.DefaultMetaFactory, audit.Scheme, audit.Scheme, false) 49 return &testWebhookHandler{ 50 t: t, 51 list: list, 52 onEvents: cb, 53 serializer: s, 54 } 55 } 56 57 type testWebhookHandler struct { 58 t *testing.T 59 60 list runtime.Object 61 onEvents func(events runtime.Object) 62 63 serializer runtime.Serializer 64 } 65 66 func (t *testWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 67 err := func() error { 68 body, err := ioutil.ReadAll(r.Body) 69 if err != nil { 70 return fmt.Errorf("read webhook request body: %v", err) 71 } 72 73 obj, _, err := t.serializer.Decode(body, nil, t.list.DeepCopyObject()) 74 if err != nil { 75 return fmt.Errorf("decode request body: %v", err) 76 } 77 if reflect.TypeOf(obj).Elem() != reflect.TypeOf(t.list).Elem() { 78 return fmt.Errorf("expected %T, got %T", t.list, obj) 79 } 80 t.onEvents(obj) 81 return nil 82 }() 83 84 if err == nil { 85 io.WriteString(w, "{}") 86 return 87 } 88 // In a goroutine, can't call Fatal. 89 assert.NoError(t.t, err, "failed to read request body") 90 http.Error(w, err.Error(), http.StatusInternalServerError) 91 } 92 93 func newWebhook(t *testing.T, endpoint string, groupVersion schema.GroupVersion) *backend { 94 config := v1.Config{ 95 Clusters: []v1.NamedCluster{ 96 {Cluster: v1.Cluster{Server: endpoint, InsecureSkipTLSVerify: true}}, 97 }, 98 } 99 f, err := ioutil.TempFile("", "k8s_audit_webhook_test_") 100 require.NoError(t, err, "creating temp file") 101 102 defer func() { 103 f.Close() 104 os.Remove(f.Name()) 105 }() 106 107 // NOTE(ericchiang): Do we need to use a proper serializer? 108 require.NoError(t, stdjson.NewEncoder(f).Encode(config), "writing kubeconfig") 109 110 retryBackoff := wait.Backoff{ 111 Duration: 500 * time.Millisecond, 112 Factor: 1.5, 113 Jitter: 0.2, 114 Steps: 5, 115 } 116 b, err := NewBackend(f.Name(), groupVersion, retryBackoff, nil) 117 require.NoError(t, err, "initializing backend") 118 119 return b.(*backend) 120 } 121 122 func TestWebhook(t *testing.T) { 123 versions := []schema.GroupVersion{auditv1.SchemeGroupVersion} 124 for _, version := range versions { 125 gotEvents := false 126 127 s := httptest.NewServer(newWebhookHandler(t, &auditv1.EventList{}, func(events runtime.Object) { 128 gotEvents = true 129 })) 130 defer s.Close() 131 132 backend := newWebhook(t, s.URL, auditv1.SchemeGroupVersion) 133 134 // Ensure this doesn't return a serialization error. 135 event := &auditinternal.Event{} 136 require.NoError(t, backend.processEvents(event), fmt.Sprintf("failed to send events, apiVersion: %s", version)) 137 require.True(t, gotEvents, fmt.Sprintf("no events received, apiVersion: %s", version)) 138 } 139 }