1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package endpoints
18
19import (
20	"bytes"
21	"fmt"
22	"net/http"
23	"net/http/httptest"
24	"regexp"
25	"sync"
26	"testing"
27	"time"
28
29	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30	"k8s.io/apimachinery/pkg/runtime"
31	"k8s.io/apimachinery/pkg/util/wait"
32	auditinternal "k8s.io/apiserver/pkg/apis/audit"
33	genericapitesting "k8s.io/apiserver/pkg/endpoints/testing"
34	"k8s.io/apiserver/pkg/registry/rest"
35)
36
37type fakeAuditSink struct {
38	lock   sync.Mutex
39	events []*auditinternal.Event
40}
41
42func (s *fakeAuditSink) ProcessEvents(evs ...*auditinternal.Event) bool {
43	s.lock.Lock()
44	defer s.lock.Unlock()
45	for _, ev := range evs {
46		e := ev.DeepCopy()
47		s.events = append(s.events, e)
48	}
49	return true
50}
51
52func (s *fakeAuditSink) Events() []*auditinternal.Event {
53	s.lock.Lock()
54	defer s.lock.Unlock()
55	return append([]*auditinternal.Event{}, s.events...)
56}
57
58func TestAudit(t *testing.T) {
59	type eventCheck func(events []*auditinternal.Event) error
60
61	// fixtures
62	simpleFoo := &genericapitesting.Simple{Other: "foo"}
63	simpleFooJSON, _ := runtime.Encode(testCodec, simpleFoo)
64
65	simpleCPrime := &genericapitesting.Simple{
66		ObjectMeta: metav1.ObjectMeta{Name: "c", Namespace: "other"},
67		Other:      "bla",
68	}
69	simpleCPrimeJSON, _ := runtime.Encode(testCodec, simpleCPrime)
70	userAgent := "audit-test"
71
72	// event checks
73	noRequestBody := func(i int) eventCheck {
74		return func(events []*auditinternal.Event) error {
75			if events[i].RequestObject == nil {
76				return nil
77			}
78			return fmt.Errorf("expected RequestBody to be nil, got non-nil '%s'", events[i].RequestObject.Raw)
79		}
80	}
81	requestBodyIs := func(i int, text string) eventCheck {
82		return func(events []*auditinternal.Event) error {
83			if events[i].RequestObject == nil {
84				if text != "" {
85					return fmt.Errorf("expected RequestBody %q, got <nil>", text)
86				}
87				return nil
88			}
89			if string(events[i].RequestObject.Raw) != text {
90				return fmt.Errorf("expected RequestBody %q, got %q", text, string(events[i].RequestObject.Raw))
91			}
92			return nil
93		}
94	}
95	requestBodyMatches := func(i int, pattern string) eventCheck {
96		return func(events []*auditinternal.Event) error {
97			if events[i].RequestObject == nil {
98				return fmt.Errorf("expected non nil request object")
99			}
100			if matched, _ := regexp.Match(pattern, events[i].RequestObject.Raw); !matched {
101				return fmt.Errorf("expected RequestBody to match %q, but didn't: %q", pattern, string(events[i].RequestObject.Raw))
102			}
103			return nil
104		}
105	}
106	noResponseBody := func(i int) eventCheck {
107		return func(events []*auditinternal.Event) error {
108			if events[i].ResponseObject == nil {
109				return nil
110			}
111			return fmt.Errorf("expected ResponseBody to be nil, got non-nil '%s'", events[i].ResponseObject.Raw)
112		}
113	}
114	responseBodyMatches := func(i int, pattern string) eventCheck {
115		return func(events []*auditinternal.Event) error {
116			if events[i].ResponseObject == nil {
117				return fmt.Errorf("expected non nil response object")
118			}
119			if matched, _ := regexp.Match(pattern, events[i].ResponseObject.Raw); !matched {
120				return fmt.Errorf("expected ResponseBody to match %q, but didn't: %q", pattern, string(events[i].ResponseObject.Raw))
121			}
122			return nil
123		}
124	}
125	requestUserAgentMatches := func(userAgent string) eventCheck {
126		return func(events []*auditinternal.Event) error {
127			for i := range events {
128				if events[i].UserAgent != userAgent {
129					return fmt.Errorf("expected request user agent to match %q, but got: %q", userAgent, events[i].UserAgent)
130				}
131			}
132			return nil
133		}
134	}
135	expectedStages := func(stages ...auditinternal.Stage) eventCheck {
136		return func(events []*auditinternal.Event) error {
137			if len(stages) != len(events) {
138				return fmt.Errorf("expected %d stages, but got %d events", len(stages), len(events))
139			}
140			for i, stage := range stages {
141				if events[i].Stage != stage {
142					return fmt.Errorf("expected stage %q, got %q", stage, events[i].Stage)
143				}
144			}
145			return nil
146		}
147	}
148
149	for _, test := range []struct {
150		desc   string
151		req    func(server string) (*http.Request, error)
152		linker runtime.SelfLinker
153		code   int
154		events int
155		checks []eventCheck
156	}{
157		{
158			"get",
159			func(server string) (*http.Request, error) {
160				return http.NewRequest("GET", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple/c", bytes.NewBuffer(simpleFooJSON))
161			},
162			selfLinker,
163			200,
164			2,
165			[]eventCheck{
166				noRequestBody(1),
167				responseBodyMatches(1, `{.*"name":"c".*}`),
168				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
169			},
170		},
171		{
172			"list",
173			func(server string) (*http.Request, error) {
174				return http.NewRequest("GET", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple?labelSelector=a%3Dfoobar", nil)
175			},
176			&setTestSelfLinker{
177				t:           t,
178				expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple",
179				namespace:   "other",
180			},
181			200,
182			2,
183			[]eventCheck{
184				noRequestBody(1),
185				responseBodyMatches(1, `{.*"name":"a".*"name":"b".*}`),
186				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
187			},
188		},
189		{
190			"create",
191			func(server string) (*http.Request, error) {
192				return http.NewRequest("POST", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(simpleFooJSON))
193			},
194			selfLinker,
195			201,
196			2,
197			[]eventCheck{
198				requestBodyIs(1, string(simpleFooJSON)),
199				responseBodyMatches(1, `{.*"foo".*}`),
200				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
201			},
202		},
203		{
204			"not-allowed-named-create",
205			func(server string) (*http.Request, error) {
206				return http.NewRequest("POST", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/named", bytes.NewBuffer(simpleFooJSON))
207			},
208			selfLinker,
209			405,
210			2,
211			[]eventCheck{
212				noRequestBody(1),  // the 405 is thrown long before the create handler would be executed
213				noResponseBody(1), // the 405 is thrown long before the create handler would be executed
214				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
215			},
216		},
217		{
218			"delete",
219			func(server string) (*http.Request, error) {
220				return http.NewRequest("DELETE", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/a", nil)
221			},
222			selfLinker,
223			200,
224			2,
225			[]eventCheck{
226				noRequestBody(1),
227				responseBodyMatches(1, `{.*"kind":"Status".*"status":"Success".*}`),
228				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
229			},
230		},
231		{
232			"delete-with-options-in-body",
233			func(server string) (*http.Request, error) {
234				return http.NewRequest("DELETE", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/a", bytes.NewBuffer([]byte(`{"kind":"DeleteOptions"}`)))
235			},
236			selfLinker,
237			200,
238			2,
239			[]eventCheck{
240				requestBodyMatches(1, "DeleteOptions"),
241				responseBodyMatches(1, `{.*"kind":"Status".*"status":"Success".*}`),
242				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
243			},
244		},
245		{
246			"update",
247			func(server string) (*http.Request, error) {
248				return http.NewRequest("PUT", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple/c", bytes.NewBuffer(simpleCPrimeJSON))
249			},
250			selfLinker,
251			200,
252			2,
253			[]eventCheck{
254				requestBodyIs(1, string(simpleCPrimeJSON)),
255				responseBodyMatches(1, `{.*"bla".*}`),
256				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
257			},
258		},
259		{
260			"update-wrong-namespace",
261			func(server string) (*http.Request, error) {
262				return http.NewRequest("PUT", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/c", bytes.NewBuffer(simpleCPrimeJSON))
263			},
264			selfLinker,
265			400,
266			2,
267			[]eventCheck{
268				requestBodyIs(1, string(simpleCPrimeJSON)),
269				responseBodyMatches(1, `"Status".*"status":"Failure".*"code":400}`),
270				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
271			},
272		},
273		{
274			"patch",
275			func(server string) (*http.Request, error) {
276				req, _ := http.NewRequest("PATCH", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple/c", bytes.NewReader([]byte(`{"labels":{"foo":"bar"}}`)))
277				req.Header.Set("Content-Type", "application/merge-patch+json; charset=UTF-8")
278				return req, nil
279			},
280			&setTestSelfLinker{
281				t:           t,
282				expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple/c",
283				name:        "c",
284				namespace:   "other",
285			},
286			200,
287			2,
288			[]eventCheck{
289				requestBodyIs(1, `{"labels":{"foo":"bar"}}`),
290				responseBodyMatches(1, `"name":"c".*"labels":{"foo":"bar"}`),
291				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete),
292			},
293		},
294		{
295			"watch",
296			func(server string) (*http.Request, error) {
297				return http.NewRequest("GET", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple?watch=true", nil)
298			},
299			&setTestSelfLinker{
300				t:           t,
301				expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple",
302				namespace:   "other",
303			},
304			200,
305			3,
306			[]eventCheck{
307				noRequestBody(2),
308				noResponseBody(2),
309				expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseStarted, auditinternal.StageResponseComplete),
310			},
311		},
312	} {
313		sink := &fakeAuditSink{}
314		handler := handleInternal(map[string]rest.Storage{
315			"simple": &SimpleRESTStorage{
316				list: []genericapitesting.Simple{
317					{
318						ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "other"},
319						Other:      "foo",
320					},
321					{
322						ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "other"},
323						Other:      "foo",
324					},
325				},
326				item: genericapitesting.Simple{
327					ObjectMeta: metav1.ObjectMeta{Name: "c", Namespace: "other", UID: "uid"},
328					Other:      "foo",
329				},
330			},
331		}, admissionControl, selfLinker, sink)
332
333		server := httptest.NewServer(handler)
334		defer server.Close()
335		client := http.Client{Timeout: 2 * time.Second}
336
337		req, err := test.req(server.URL)
338		if err != nil {
339			t.Errorf("[%s] error creating the request: %v", test.desc, err)
340		}
341
342		req.Header.Set("User-Agent", userAgent)
343
344		response, err := client.Do(req)
345		if err != nil {
346			t.Errorf("[%s] error: %v", test.desc, err)
347		}
348
349		if response.StatusCode != test.code {
350			t.Errorf("[%s] expected http code %d, got %#v", test.desc, test.code, response)
351		}
352
353		// close body because the handler might block in Flush, unable to send the remaining event.
354		response.Body.Close()
355
356		// wait for events to arrive, at least the given number in the test
357		events := []*auditinternal.Event{}
358		err = wait.Poll(50*time.Millisecond, wait.ForeverTestTimeout, wait.ConditionFunc(func() (done bool, err error) {
359			events = sink.Events()
360			return len(events) >= test.events, nil
361		}))
362		if err != nil {
363			t.Errorf("[%s] timeout waiting for events", test.desc)
364		}
365
366		if got := len(events); got != test.events {
367			t.Errorf("[%s] expected %d audit events, got %d", test.desc, test.events, got)
368		} else {
369			for i, check := range test.checks {
370				err := check(events)
371				if err != nil {
372					t.Errorf("[%s,%d] %v", test.desc, i, err)
373				}
374			}
375
376			if err := requestUserAgentMatches(userAgent)(events); err != nil {
377				t.Errorf("[%s] %v", test.desc, err)
378			}
379		}
380
381		if len(events) > 0 {
382			status := events[len(events)-1].ResponseStatus
383			if status == nil {
384				t.Errorf("[%s] expected non-nil ResponseStatus in last event", test.desc)
385			} else if int(status.Code) != test.code {
386				t.Errorf("[%s] expected ResponseStatus.Code=%d, got %d", test.desc, test.code, status.Code)
387			}
388		}
389	}
390}
391