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  }