| /* |
| Copyright 2017 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 webhook |
| |
| import ( |
| stdjson "encoding/json" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "net/http" |
| "net/http/httptest" |
| "os" |
| "reflect" |
| "testing" |
| |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/require" |
| |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/schema" |
| "k8s.io/apimachinery/pkg/runtime/serializer/json" |
| auditinternal "k8s.io/apiserver/pkg/apis/audit" |
| auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" |
| auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" |
| "k8s.io/apiserver/pkg/audit" |
| "k8s.io/client-go/tools/clientcmd/api/v1" |
| ) |
| |
| // newWebhookHandler returns a handler which receives webhook events and decodes the |
| // request body. The caller passes a callback which is called on each webhook POST. |
| // The object passed to cb is of the same type as list. |
| func newWebhookHandler(t *testing.T, list runtime.Object, cb func(events runtime.Object)) http.Handler { |
| s := json.NewSerializer(json.DefaultMetaFactory, audit.Scheme, audit.Scheme, false) |
| return &testWebhookHandler{ |
| t: t, |
| list: list, |
| onEvents: cb, |
| serializer: s, |
| } |
| } |
| |
| type testWebhookHandler struct { |
| t *testing.T |
| |
| list runtime.Object |
| onEvents func(events runtime.Object) |
| |
| serializer runtime.Serializer |
| } |
| |
| func (t *testWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| err := func() error { |
| body, err := ioutil.ReadAll(r.Body) |
| if err != nil { |
| return fmt.Errorf("read webhook request body: %v", err) |
| } |
| |
| obj, _, err := t.serializer.Decode(body, nil, t.list.DeepCopyObject()) |
| if err != nil { |
| return fmt.Errorf("decode request body: %v", err) |
| } |
| if reflect.TypeOf(obj).Elem() != reflect.TypeOf(t.list).Elem() { |
| return fmt.Errorf("expected %T, got %T", t.list, obj) |
| } |
| t.onEvents(obj) |
| return nil |
| }() |
| |
| if err == nil { |
| io.WriteString(w, "{}") |
| return |
| } |
| // In a goroutine, can't call Fatal. |
| assert.NoError(t.t, err, "failed to read request body") |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| } |
| |
| func newWebhook(t *testing.T, endpoint string, groupVersion schema.GroupVersion) *backend { |
| config := v1.Config{ |
| Clusters: []v1.NamedCluster{ |
| {Cluster: v1.Cluster{Server: endpoint, InsecureSkipTLSVerify: true}}, |
| }, |
| } |
| f, err := ioutil.TempFile("", "k8s_audit_webhook_test_") |
| require.NoError(t, err, "creating temp file") |
| |
| defer func() { |
| f.Close() |
| os.Remove(f.Name()) |
| }() |
| |
| // NOTE(ericchiang): Do we need to use a proper serializer? |
| require.NoError(t, stdjson.NewEncoder(f).Encode(config), "writing kubeconfig") |
| |
| b, err := NewBackend(f.Name(), groupVersion, DefaultInitialBackoff) |
| require.NoError(t, err, "initializing backend") |
| |
| return b.(*backend) |
| } |
| |
| func TestWebhook(t *testing.T) { |
| versions := []schema.GroupVersion{auditv1.SchemeGroupVersion, auditv1beta1.SchemeGroupVersion} |
| for _, version := range versions { |
| gotEvents := false |
| |
| s := httptest.NewServer(newWebhookHandler(t, &auditv1.EventList{}, func(events runtime.Object) { |
| gotEvents = true |
| })) |
| defer s.Close() |
| |
| backend := newWebhook(t, s.URL, auditv1.SchemeGroupVersion) |
| |
| // Ensure this doesn't return a serialization error. |
| event := &auditinternal.Event{} |
| require.NoError(t, backend.processEvents(event), fmt.Sprintf("failed to send events, apiVersion: %s", version)) |
| require.True(t, gotEvents, fmt.Sprintf("no events received, apiVersion: %s", version)) |
| } |
| } |