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