| /* |
| Copyright 2016 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 filters |
| |
| import ( |
| "bufio" |
| "net" |
| "net/http" |
| "net/http/httptest" |
| "reflect" |
| "sync" |
| "testing" |
| "time" |
| |
| "github.com/pborman/uuid" |
| |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/types" |
| "k8s.io/apimachinery/pkg/util/wait" |
| auditinternal "k8s.io/apiserver/pkg/apis/audit" |
| "k8s.io/apiserver/pkg/audit/policy" |
| "k8s.io/apiserver/pkg/authentication/user" |
| "k8s.io/apiserver/pkg/endpoints/request" |
| ) |
| |
| type fakeAuditSink struct { |
| lock sync.Mutex |
| events []*auditinternal.Event |
| } |
| |
| func (s *fakeAuditSink) ProcessEvents(evs ...*auditinternal.Event) { |
| s.lock.Lock() |
| defer s.lock.Unlock() |
| for _, e := range evs { |
| event := e.DeepCopy() |
| s.events = append(s.events, event) |
| } |
| } |
| |
| func (s *fakeAuditSink) Events() []*auditinternal.Event { |
| s.lock.Lock() |
| defer s.lock.Unlock() |
| return append([]*auditinternal.Event{}, s.events...) |
| } |
| |
| func (s *fakeAuditSink) Pop(timeout time.Duration) (*auditinternal.Event, error) { |
| var result *auditinternal.Event |
| err := wait.Poll(50*time.Millisecond, wait.ForeverTestTimeout, wait.ConditionFunc(func() (done bool, err error) { |
| s.lock.Lock() |
| defer s.lock.Unlock() |
| if len(s.events) == 0 { |
| return false, nil |
| } |
| result = s.events[0] |
| s.events = s.events[1:] |
| return true, nil |
| })) |
| return result, err |
| } |
| |
| type simpleResponseWriter struct{} |
| |
| var _ http.ResponseWriter = &simpleResponseWriter{} |
| |
| func (*simpleResponseWriter) WriteHeader(code int) {} |
| func (*simpleResponseWriter) Write(bs []byte) (int, error) { return len(bs), nil } |
| func (*simpleResponseWriter) Header() http.Header { return http.Header{} } |
| |
| type fancyResponseWriter struct { |
| simpleResponseWriter |
| } |
| |
| func (*fancyResponseWriter) CloseNotify() <-chan bool { return nil } |
| |
| func (*fancyResponseWriter) Flush() {} |
| |
| func (*fancyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return nil, nil, nil } |
| |
| func TestConstructResponseWriter(t *testing.T) { |
| actual := decorateResponseWriter(&simpleResponseWriter{}, nil, nil, nil) |
| switch v := actual.(type) { |
| case *auditResponseWriter: |
| default: |
| t.Errorf("Expected auditResponseWriter, got %v", reflect.TypeOf(v)) |
| } |
| |
| actual = decorateResponseWriter(&fancyResponseWriter{}, nil, nil, nil) |
| switch v := actual.(type) { |
| case *fancyResponseWriterDelegator: |
| default: |
| t.Errorf("Expected fancyResponseWriterDelegator, got %v", reflect.TypeOf(v)) |
| } |
| } |
| |
| func TestDecorateResponseWriterWithoutChannel(t *testing.T) { |
| ev := &auditinternal.Event{} |
| actual := decorateResponseWriter(&simpleResponseWriter{}, ev, nil, nil) |
| |
| // write status. This will not block because firstEventSentCh is nil |
| actual.WriteHeader(42) |
| if ev.ResponseStatus == nil { |
| t.Fatalf("Expected ResponseStatus to be non-nil") |
| } |
| if ev.ResponseStatus.Code != 42 { |
| t.Errorf("expected status code 42, got %d", ev.ResponseStatus.Code) |
| } |
| } |
| |
| func TestDecorateResponseWriterWithImplicitWrite(t *testing.T) { |
| ev := &auditinternal.Event{} |
| actual := decorateResponseWriter(&simpleResponseWriter{}, ev, nil, nil) |
| |
| // write status. This will not block because firstEventSentCh is nil |
| actual.Write([]byte("foo")) |
| if ev.ResponseStatus == nil { |
| t.Fatalf("Expected ResponseStatus to be non-nil") |
| } |
| if ev.ResponseStatus.Code != 200 { |
| t.Errorf("expected status code 200, got %d", ev.ResponseStatus.Code) |
| } |
| } |
| |
| func TestDecorateResponseWriterChannel(t *testing.T) { |
| sink := &fakeAuditSink{} |
| ev := &auditinternal.Event{} |
| actual := decorateResponseWriter(&simpleResponseWriter{}, ev, sink, nil) |
| |
| done := make(chan struct{}) |
| go func() { |
| t.Log("Writing status code 42") |
| actual.WriteHeader(42) |
| t.Log("Finished writing status code 42") |
| close(done) |
| |
| actual.Write([]byte("foo")) |
| }() |
| |
| // sleep some time to give write the possibility to do wrong stuff |
| time.Sleep(100 * time.Millisecond) |
| |
| t.Log("Waiting for event in the channel") |
| ev1, err := sink.Pop(time.Second) |
| if err != nil { |
| t.Fatal("Timeout waiting for events") |
| } |
| t.Logf("Seen event with status %v", ev1.ResponseStatus) |
| |
| if !reflect.DeepEqual(ev, ev1) { |
| t.Fatalf("ev1 and ev must be equal") |
| } |
| |
| <-done |
| t.Log("Seen the go routine finished") |
| |
| // write again |
| _, err = actual.Write([]byte("foo")) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| } |
| |
| type fakeHTTPHandler struct{} |
| |
| func (*fakeHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
| w.WriteHeader(200) |
| } |
| |
| func TestAudit(t *testing.T) { |
| shortRunningPath := "/api/v1/namespaces/default/pods/foo" |
| longRunningPath := "/api/v1/namespaces/default/pods?watch=true" |
| |
| delay := 500 * time.Millisecond |
| |
| for _, test := range []struct { |
| desc string |
| path string |
| verb string |
| auditID string |
| omitStages []auditinternal.Stage |
| handler func(http.ResponseWriter, *http.Request) |
| expected []auditinternal.Event |
| respHeader bool |
| }{ |
| // short running requests with read-only verb |
| { |
| "read-only empty", |
| shortRunningPath, |
| "GET", |
| "", |
| nil, |
| func(http.ResponseWriter, *http.Request) {}, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "get", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "get", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| false, |
| }, |
| { |
| "short running with auditID", |
| shortRunningPath, |
| "GET", |
| uuid.NewRandom().String(), |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "get", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "get", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "read-only panic", |
| shortRunningPath, |
| "GET", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| panic("kaboom") |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "get", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StagePanic, |
| Verb: "get", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 500}, |
| }, |
| }, |
| false, |
| }, |
| // short running request with non-read-only verb |
| { |
| "writing empty", |
| shortRunningPath, |
| "PUT", |
| "", |
| nil, |
| func(http.ResponseWriter, *http.Request) {}, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| false, |
| }, |
| { |
| "writing sleep", |
| shortRunningPath, |
| "PUT", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| time.Sleep(delay) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "writing 403+write", |
| shortRunningPath, |
| "PUT", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.WriteHeader(403) |
| w.Write([]byte("foo")) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 403}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "writing panic", |
| shortRunningPath, |
| "PUT", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| panic("kaboom") |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StagePanic, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 500}, |
| }, |
| }, |
| false, |
| }, |
| { |
| "writing write+panic", |
| shortRunningPath, |
| "PUT", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| panic("kaboom") |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| }, |
| { |
| Stage: auditinternal.StagePanic, |
| Verb: "update", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 500}, |
| }, |
| }, |
| true, |
| }, |
| // long running requests |
| { |
| "empty longrunning", |
| longRunningPath, |
| "GET", |
| "", |
| nil, |
| func(http.ResponseWriter, *http.Request) {}, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseStarted, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| false, |
| }, |
| { |
| "empty longrunning with audit id", |
| longRunningPath, |
| "GET", |
| uuid.NewRandom().String(), |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseStarted, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "sleep longrunning", |
| longRunningPath, |
| "GET", |
| "", |
| nil, |
| func(http.ResponseWriter, *http.Request) { |
| time.Sleep(delay) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseStarted, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| false, |
| }, |
| { |
| "sleep+403 longrunning", |
| longRunningPath, |
| "GET", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| time.Sleep(delay) |
| w.WriteHeader(403) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseStarted, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 403}, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 403}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "write longrunning", |
| longRunningPath, |
| "GET", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseStarted, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "403+write longrunning", |
| longRunningPath, |
| "GET", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.WriteHeader(403) |
| w.Write([]byte("foo")) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseStarted, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 403}, |
| }, |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 403}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "panic longrunning", |
| longRunningPath, |
| "GET", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| panic("kaboom") |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StagePanic, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 500}, |
| }, |
| }, |
| false, |
| }, |
| { |
| "write+panic longrunning", |
| longRunningPath, |
| "GET", |
| "", |
| nil, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| panic("kaboom") |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageRequestReceived, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| }, |
| { |
| Stage: auditinternal.StageResponseStarted, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| { |
| Stage: auditinternal.StagePanic, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 500}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "omit RequestReceived", |
| shortRunningPath, |
| "GET", |
| "", |
| []auditinternal.Stage{auditinternal.StageRequestReceived}, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StageResponseComplete, |
| Verb: "get", |
| RequestURI: shortRunningPath, |
| ResponseStatus: &metav1.Status{Code: 200}, |
| }, |
| }, |
| true, |
| }, |
| { |
| "emit Panic only", |
| longRunningPath, |
| "GET", |
| "", |
| []auditinternal.Stage{auditinternal.StageRequestReceived, auditinternal.StageResponseStarted, auditinternal.StageResponseComplete}, |
| func(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte("foo")) |
| panic("kaboom") |
| }, |
| []auditinternal.Event{ |
| { |
| Stage: auditinternal.StagePanic, |
| Verb: "watch", |
| RequestURI: longRunningPath, |
| ResponseStatus: &metav1.Status{Code: 500}, |
| }, |
| }, |
| true, |
| }, |
| } { |
| t.Run(test.desc, func(t *testing.T) { |
| sink := &fakeAuditSink{} |
| policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, test.omitStages) |
| handler := WithAudit(http.HandlerFunc(test.handler), sink, policyChecker, func(r *http.Request, ri *request.RequestInfo) bool { |
| // simplified long-running check |
| return ri.Verb == "watch" |
| }) |
| |
| req, _ := http.NewRequest(test.verb, test.path, nil) |
| req = withTestContext(req, &user.DefaultInfo{Name: "admin"}, nil) |
| if test.auditID != "" { |
| req.Header.Add("Audit-ID", test.auditID) |
| } |
| req.RemoteAddr = "127.0.0.1" |
| |
| func() { |
| defer func() { |
| recover() |
| }() |
| handler.ServeHTTP(httptest.NewRecorder(), req) |
| }() |
| |
| events := sink.Events() |
| t.Logf("audit log: %v", events) |
| |
| if len(events) != len(test.expected) { |
| t.Fatalf("Unexpected amount of lines in audit log: %d", len(events)) |
| } |
| expectedID := types.UID("") |
| for i, expect := range test.expected { |
| event := events[i] |
| if "admin" != event.User.Username { |
| t.Errorf("Unexpected username: %s", event.User.Username) |
| } |
| if event.Stage != expect.Stage { |
| t.Errorf("Unexpected Stage: %s", event.Stage) |
| } |
| if event.Verb != expect.Verb { |
| t.Errorf("Unexpected Verb: %s", event.Verb) |
| } |
| if event.RequestURI != expect.RequestURI { |
| t.Errorf("Unexpected RequestURI: %s", event.RequestURI) |
| } |
| |
| if test.auditID != "" && event.AuditID != types.UID(test.auditID) { |
| t.Errorf("Unexpected AuditID in audit event, AuditID should be the same with Audit-ID http header") |
| } |
| if expectedID == types.UID("") { |
| expectedID = event.AuditID |
| } else if expectedID != event.AuditID { |
| t.Errorf("Audits for one request should share the same AuditID, %s differs from %s", expectedID, event.AuditID) |
| } |
| if event.ObjectRef.APIVersion != "v1" { |
| t.Errorf("Unexpected apiVersion: %s", event.ObjectRef.APIVersion) |
| } |
| if event.ObjectRef.APIGroup != "" { |
| t.Errorf("Unexpected apiGroup: %s", event.ObjectRef.APIGroup) |
| } |
| if (event.ResponseStatus == nil) != (expect.ResponseStatus == nil) { |
| t.Errorf("Unexpected ResponseStatus: %v", event.ResponseStatus) |
| continue |
| } |
| if (event.ResponseStatus != nil) && (event.ResponseStatus.Code != expect.ResponseStatus.Code) { |
| t.Errorf("Unexpected status code : %d", event.ResponseStatus.Code) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestAuditNoPanicOnNilUser(t *testing.T) { |
| policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, nil) |
| handler := WithAudit(&fakeHTTPHandler{}, &fakeAuditSink{}, policyChecker, nil) |
| req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil) |
| req = withTestContext(req, nil, nil) |
| req.RemoteAddr = "127.0.0.1" |
| handler.ServeHTTP(httptest.NewRecorder(), req) |
| } |
| |
| func TestAuditLevelNone(t *testing.T) { |
| sink := &fakeAuditSink{} |
| var handler http.Handler |
| handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { |
| w.WriteHeader(200) |
| }) |
| policyChecker := policy.FakeChecker(auditinternal.LevelNone, nil) |
| handler = WithAudit(handler, sink, policyChecker, nil) |
| |
| req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil) |
| req.RemoteAddr = "127.0.0.1" |
| req = withTestContext(req, &user.DefaultInfo{Name: "admin"}, nil) |
| |
| handler.ServeHTTP(httptest.NewRecorder(), req) |
| if len(sink.events) > 0 { |
| t.Errorf("Generated events, but should not have: %#v", sink.events) |
| } |
| } |
| |
| func TestAuditIDHttpHeader(t *testing.T) { |
| for _, test := range []struct { |
| desc string |
| requestHeader string |
| level auditinternal.Level |
| expectedHeader bool |
| }{ |
| { |
| "no http header when there is no audit", |
| "", |
| auditinternal.LevelNone, |
| false, |
| }, |
| { |
| "no http header when there is no audit even the request header specified", |
| uuid.NewRandom().String(), |
| auditinternal.LevelNone, |
| false, |
| }, |
| { |
| "server generated header", |
| "", |
| auditinternal.LevelRequestResponse, |
| true, |
| }, |
| { |
| "user provided header", |
| uuid.NewRandom().String(), |
| auditinternal.LevelRequestResponse, |
| true, |
| }, |
| } { |
| sink := &fakeAuditSink{} |
| var handler http.Handler |
| handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { |
| w.WriteHeader(200) |
| }) |
| policyChecker := policy.FakeChecker(test.level, nil) |
| handler = WithAudit(handler, sink, policyChecker, nil) |
| |
| req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil) |
| req.RemoteAddr = "127.0.0.1" |
| req = withTestContext(req, &user.DefaultInfo{Name: "admin"}, nil) |
| if test.requestHeader != "" { |
| req.Header.Add("Audit-ID", test.requestHeader) |
| } |
| |
| w := httptest.NewRecorder() |
| handler.ServeHTTP(w, req) |
| resp := w.Result() |
| if test.expectedHeader { |
| if resp.Header.Get("Audit-ID") == "" { |
| t.Errorf("[%s] expected Audit-ID http header returned, but not returned", test.desc) |
| continue |
| } |
| // if get Audit-ID returned, it should be the same with the requested one |
| if test.requestHeader != "" && resp.Header.Get("Audit-ID") != test.requestHeader { |
| t.Errorf("[%s] returned audit http header is not the same with the requested http header, expected: %s, get %s", test.desc, test.requestHeader, resp.Header.Get("Audit-ID")) |
| } |
| } else { |
| if resp.Header.Get("Audit-ID") != "" { |
| t.Errorf("[%s] expected no Audit-ID http header returned, but got %s", test.desc, resp.Header.Get("Audit-ID")) |
| } |
| } |
| } |
| } |
| |
| func withTestContext(req *http.Request, user user.Info, audit *auditinternal.Event) *http.Request { |
| ctx := req.Context() |
| if user != nil { |
| ctx = request.WithUser(ctx, user) |
| } |
| if audit != nil { |
| ctx = request.WithAuditEvent(ctx, audit) |
| } |
| if info, err := newTestRequestInfoResolver().NewRequestInfo(req); err == nil { |
| ctx = request.WithRequestInfo(ctx, info) |
| } |
| return req.WithContext(ctx) |
| } |