k8s.io/apiserver@v0.31.1/pkg/endpoints/audit_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 endpoints 18 19 import ( 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 37 type fakeAuditSink struct { 38 lock sync.Mutex 39 events []*auditinternal.Event 40 } 41 42 func (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 52 func (s *fakeAuditSink) Events() []*auditinternal.Event { 53 s.lock.Lock() 54 defer s.lock.Unlock() 55 return append([]*auditinternal.Event{}, s.events...) 56 } 57 58 func 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 code int 153 events int 154 checks []eventCheck 155 }{ 156 { 157 "get", 158 func(server string) (*http.Request, error) { 159 return http.NewRequest("GET", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple/c", bytes.NewBuffer(simpleFooJSON)) 160 }, 161 200, 162 2, 163 []eventCheck{ 164 noRequestBody(1), 165 responseBodyMatches(1, `{.*"name":"c".*}`), 166 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 167 }, 168 }, 169 { 170 "list", 171 func(server string) (*http.Request, error) { 172 return http.NewRequest("GET", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple?labelSelector=a%3Dfoobar", nil) 173 }, 174 200, 175 2, 176 []eventCheck{ 177 noRequestBody(1), 178 responseBodyMatches(1, `{.*"name":"a".*"name":"b".*}`), 179 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 180 }, 181 }, 182 { 183 "create", 184 func(server string) (*http.Request, error) { 185 return http.NewRequest("POST", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(simpleFooJSON)) 186 }, 187 201, 188 2, 189 []eventCheck{ 190 requestBodyIs(1, string(simpleFooJSON)), 191 responseBodyMatches(1, `{.*"foo".*}`), 192 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 193 }, 194 }, 195 { 196 "not-allowed-named-create", 197 func(server string) (*http.Request, error) { 198 return http.NewRequest("POST", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/named", bytes.NewBuffer(simpleFooJSON)) 199 }, 200 405, 201 2, 202 []eventCheck{ 203 noRequestBody(1), // the 405 is thrown long before the create handler would be executed 204 noResponseBody(1), // the 405 is thrown long before the create handler would be executed 205 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 206 }, 207 }, 208 { 209 "delete", 210 func(server string) (*http.Request, error) { 211 return http.NewRequest("DELETE", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/a", nil) 212 }, 213 200, 214 2, 215 []eventCheck{ 216 noRequestBody(1), 217 responseBodyMatches(1, `{.*"kind":"Status".*"status":"Success".*}`), 218 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 219 }, 220 }, 221 { 222 "delete-with-options-in-body", 223 func(server string) (*http.Request, error) { 224 return http.NewRequest("DELETE", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/a", bytes.NewBuffer([]byte(`{"kind":"DeleteOptions"}`))) 225 }, 226 200, 227 2, 228 []eventCheck{ 229 requestBodyMatches(1, "DeleteOptions"), 230 responseBodyMatches(1, `{.*"kind":"Status".*"status":"Success".*}`), 231 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 232 }, 233 }, 234 { 235 "update", 236 func(server string) (*http.Request, error) { 237 return http.NewRequest("PUT", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple/c", bytes.NewBuffer(simpleCPrimeJSON)) 238 }, 239 200, 240 2, 241 []eventCheck{ 242 requestBodyIs(1, string(simpleCPrimeJSON)), 243 responseBodyMatches(1, `{.*"bla".*}`), 244 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 245 }, 246 }, 247 { 248 "update-wrong-namespace", 249 func(server string) (*http.Request, error) { 250 return http.NewRequest("PUT", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/c", bytes.NewBuffer(simpleCPrimeJSON)) 251 }, 252 400, 253 2, 254 []eventCheck{ 255 requestBodyIs(1, string(simpleCPrimeJSON)), 256 responseBodyMatches(1, `"Status".*"status":"Failure".*"code":400}`), 257 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 258 }, 259 }, 260 { 261 "patch", 262 func(server string) (*http.Request, error) { 263 req, _ := http.NewRequest("PATCH", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple/c", bytes.NewReader([]byte(`{"labels":{"foo":"bar"}}`))) 264 req.Header.Set("Content-Type", "application/merge-patch+json; charset=UTF-8") 265 return req, nil 266 }, 267 200, 268 2, 269 []eventCheck{ 270 requestBodyIs(1, `{"labels":{"foo":"bar"}}`), 271 responseBodyMatches(1, `"name":"c".*"labels":{"foo":"bar"}`), 272 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseComplete), 273 }, 274 }, 275 { 276 "watch", 277 func(server string) (*http.Request, error) { 278 return http.NewRequest("GET", server+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/simple?watch=true", nil) 279 }, 280 200, 281 3, 282 []eventCheck{ 283 noRequestBody(2), 284 noResponseBody(2), 285 expectedStages(auditinternal.StageRequestReceived, auditinternal.StageResponseStarted, auditinternal.StageResponseComplete), 286 }, 287 }, 288 } { 289 t.Run(test.desc, func(t *testing.T) { 290 sink := &fakeAuditSink{} 291 handler := handleInternal(map[string]rest.Storage{ 292 "simple": &SimpleRESTStorage{ 293 list: []genericapitesting.Simple{ 294 { 295 ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "other"}, 296 Other: "foo", 297 }, 298 { 299 ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "other"}, 300 Other: "foo", 301 }, 302 }, 303 item: genericapitesting.Simple{ 304 ObjectMeta: metav1.ObjectMeta{Name: "c", Namespace: "other", UID: "uid"}, 305 Other: "foo", 306 }, 307 }, 308 }, admissionControl, sink) 309 310 server := httptest.NewServer(handler) 311 defer server.Close() 312 client := http.Client{Timeout: 2 * time.Second} 313 314 req, err := test.req(server.URL) 315 if err != nil { 316 t.Errorf("[%s] error creating the request: %v", test.desc, err) 317 } 318 319 req.Header.Set("User-Agent", userAgent) 320 321 response, err := client.Do(req) 322 if err != nil { 323 t.Errorf("[%s] error: %v", test.desc, err) 324 } 325 326 if response.StatusCode != test.code { 327 t.Errorf("[%s] expected http code %d, got %#v", test.desc, test.code, response) 328 } 329 330 // close body because the handler might block in Flush, unable to send the remaining event. 331 response.Body.Close() 332 333 // wait for events to arrive, at least the given number in the test 334 events := []*auditinternal.Event{} 335 err = wait.Poll(50*time.Millisecond, testTimeout(t), wait.ConditionFunc(func() (done bool, err error) { 336 events = sink.Events() 337 return len(events) >= test.events, nil 338 })) 339 if err != nil { 340 t.Errorf("[%s] timeout waiting for events", test.desc) 341 } 342 343 if got := len(events); got != test.events { 344 t.Errorf("[%s] expected %d audit events, got %d", test.desc, test.events, got) 345 } else { 346 for i, check := range test.checks { 347 err := check(events) 348 if err != nil { 349 t.Errorf("[%s,%d] %v", test.desc, i, err) 350 } 351 } 352 353 if err := requestUserAgentMatches(userAgent)(events); err != nil { 354 t.Errorf("[%s] %v", test.desc, err) 355 } 356 } 357 358 if len(events) > 0 { 359 status := events[len(events)-1].ResponseStatus 360 if status == nil { 361 t.Errorf("[%s] expected non-nil ResponseStatus in last event", test.desc) 362 } else if int(status.Code) != test.code { 363 t.Errorf("[%s] expected ResponseStatus.Code=%d, got %d", test.desc, test.code, status.Code) 364 } 365 } 366 }) 367 } 368 } 369 370 // testTimeout returns the minimimum of the "ForeverTestTimeout" and the testing deadline (with 371 // cleanup time). 372 func testTimeout(t *testing.T) time.Duration { 373 defaultTimeout := wait.ForeverTestTimeout 374 const cleanupTime = 5 * time.Second 375 if deadline, ok := t.Deadline(); ok { 376 maxTimeout := time.Until(deadline) - cleanupTime 377 if maxTimeout < defaultTimeout { 378 return maxTimeout 379 } 380 } 381 return defaultTimeout 382 }