enhance unit tests of advance audit feature

This change does three things:
    1. use auditinternal for unit test in filter stage
    2. add a seperate unit test for Audit-ID http header
    3. add unit test for audit log backend
pull/6/head
CaoShufeng 2017-09-06 21:31:14 +08:00
parent 8884f984e4
commit c030026b54
4 changed files with 348 additions and 388 deletions

View File

@ -29,16 +29,12 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/audit/log:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/audit/webhook:go_default_library",
],
)

View File

@ -18,14 +18,10 @@ package filters
import (
"bufio"
"bytes"
"fmt"
"net"
"net/http"
"net/http/httptest"
"reflect"
"regexp"
"strings"
"sync"
"testing"
"time"
@ -33,18 +29,12 @@ import (
"github.com/pborman/uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/audit/policy"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
pluginlog "k8s.io/apiserver/plugin/pkg/audit/log"
// import to call webhook's init() function to register audit.Event to schema
_ "k8s.io/apiserver/plugin/pkg/audit/webhook"
)
type fakeAuditSink struct {
@ -55,7 +45,10 @@ type fakeAuditSink struct {
func (s *fakeAuditSink) ProcessEvents(evs ...*auditinternal.Event) {
s.lock.Lock()
defer s.lock.Unlock()
s.events = append(s.events, evs...)
for _, e := range evs {
event := e.DeepCopy()
s.events = append(s.events, event)
}
}
func (s *fakeAuditSink) Events() []*auditinternal.Event {
@ -166,7 +159,7 @@ func TestDecorateResponseWriterChannel(t *testing.T) {
}
t.Logf("Seen event with status %v", ev1.ResponseStatus)
if ev != ev1 {
if !reflect.DeepEqual(ev, ev1) {
t.Fatalf("ev1 and ev must be equal")
}
@ -186,288 +179,7 @@ func (*fakeHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(200)
}
func TestAuditLegacy(t *testing.T) {
writingShortRunningPrefix := func(stage string) string {
return fmt.Sprintf(`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" stage="%s" ip="127.0.0.1" method="update" user="admin" groups="<none>" as="<self>" asgroups="<lookup>" namespace="default" uri="/api/v1/namespaces/default/pods/foo"`, stage)
}
readOnlyShortRunningPrefix := func(stage string) string {
return fmt.Sprintf(`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" stage="%s" ip="127.0.0.1" method="get" user="admin" groups="<none>" as="<self>" asgroups="<lookup>" namespace="default" uri="/api/v1/namespaces/default/pods/foo"`, stage)
}
longRunningPrefix := func(stage string) string {
return fmt.Sprintf(`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" stage="%s" ip="127.0.0.1" method="watch" user="admin" groups="<none>" as="<self>" asgroups="<lookup>" namespace="default" uri="/api/v1/namespaces/default/pods\?watch=true"`, stage)
}
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
omitStages []auditinternal.Stage
handler func(http.ResponseWriter, *http.Request)
expected []string
}{
// short running requests with read-only verb
{
"read-only empty",
shortRunningPath,
"GET",
nil,
func(http.ResponseWriter, *http.Request) {},
[]string{
readOnlyShortRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
readOnlyShortRunningPrefix(auditinternal.StageResponseComplete) + ` response="200"`,
},
},
{
"read-only panic",
shortRunningPath,
"GET",
nil,
func(w http.ResponseWriter, req *http.Request) {
panic("kaboom")
},
[]string{
readOnlyShortRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
readOnlyShortRunningPrefix(auditinternal.StagePanic) + ` response="500"`,
},
},
// short running request with non-read-only verb
{
"writing empty",
shortRunningPath,
"PUT",
nil,
func(http.ResponseWriter, *http.Request) {},
[]string{
writingShortRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
writingShortRunningPrefix(auditinternal.StageResponseComplete) + ` response="200"`,
},
},
{
"writing sleep",
shortRunningPath,
"PUT",
nil,
func(http.ResponseWriter, *http.Request) {
time.Sleep(delay)
},
[]string{
writingShortRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
writingShortRunningPrefix(auditinternal.StageResponseComplete) + ` response="200"`,
},
},
{
"writing 403+write",
shortRunningPath,
"PUT",
nil,
func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(403)
w.Write([]byte("foo"))
},
[]string{
writingShortRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
writingShortRunningPrefix(auditinternal.StageResponseComplete) + ` response="403"`,
},
},
{
"writing panic",
shortRunningPath,
"PUT",
nil,
func(w http.ResponseWriter, req *http.Request) {
panic("kaboom")
},
[]string{
writingShortRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
writingShortRunningPrefix(auditinternal.StagePanic) + ` response="500"`,
},
},
{
"writing write+panic",
shortRunningPath,
"PUT",
nil,
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
panic("kaboom")
},
[]string{
writingShortRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
writingShortRunningPrefix(auditinternal.StagePanic) + ` response="500"`,
},
},
// long running requests
{
"empty longrunning",
longRunningPath,
"GET",
nil,
func(http.ResponseWriter, *http.Request) {},
[]string{
longRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
longRunningPrefix(auditinternal.StageResponseStarted) + ` response="200"`,
longRunningPrefix(auditinternal.StageResponseComplete) + ` response="200"`,
},
},
{
"sleep longrunning",
longRunningPath,
"GET",
nil,
func(http.ResponseWriter, *http.Request) {
time.Sleep(delay)
},
[]string{
longRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
longRunningPrefix(auditinternal.StageResponseStarted) + ` response="200"`,
longRunningPrefix(auditinternal.StageResponseComplete) + ` response="200"`,
},
},
{
"sleep+403 longrunning",
longRunningPath,
"GET",
nil,
func(w http.ResponseWriter, req *http.Request) {
time.Sleep(delay)
w.WriteHeader(403)
},
[]string{
longRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
longRunningPrefix(auditinternal.StageResponseStarted) + ` response="403"`,
longRunningPrefix(auditinternal.StageResponseComplete) + ` response="403"`,
},
},
{
"write longrunning",
longRunningPath,
"GET",
nil,
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
},
[]string{
longRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
longRunningPrefix(auditinternal.StageResponseStarted) + ` response="200"`,
longRunningPrefix(auditinternal.StageResponseComplete) + ` response="200"`,
},
},
{
"403+write longrunning",
longRunningPath,
"GET",
nil,
func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(403)
w.Write([]byte("foo"))
},
[]string{
longRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
longRunningPrefix(auditinternal.StageResponseStarted) + ` response="403"`,
longRunningPrefix(auditinternal.StageResponseComplete) + ` response="403"`,
},
},
{
"panic longrunning",
longRunningPath,
"GET",
nil,
func(w http.ResponseWriter, req *http.Request) {
panic("kaboom")
},
[]string{
longRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
longRunningPrefix(auditinternal.StagePanic) + ` response="500"`,
},
},
{
"write+panic longrunning",
longRunningPath,
"GET",
nil,
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
panic("kaboom")
},
[]string{
longRunningPrefix(auditinternal.StageRequestReceived) + ` response="<deferred>"`,
longRunningPrefix(auditinternal.StageResponseStarted) + ` response="200"`,
longRunningPrefix(auditinternal.StagePanic) + ` response="500"`,
},
},
{
"omit RequestReceived",
shortRunningPath,
"GET",
[]auditinternal.Stage{auditinternal.StageRequestReceived},
func(http.ResponseWriter, *http.Request) {},
[]string{
readOnlyShortRunningPrefix(auditinternal.StageResponseComplete) + ` response="200"`,
},
},
{
"emit painc only",
longRunningPath,
"GET",
[]auditinternal.Stage{auditinternal.StageRequestReceived, auditinternal.StageResponseStarted, auditinternal.StageResponseComplete},
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
panic("kaboom")
},
[]string{
longRunningPrefix(auditinternal.StagePanic) + ` response="500"`,
},
},
} {
var buf bytes.Buffer
backend := pluginlog.NewBackend(&buf, pluginlog.FormatLegacy, auditv1beta1.SchemeGroupVersion)
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, test.omitStages)
handler := WithAudit(http.HandlerFunc(test.handler), &fakeRequestContextMapper{
user: &user.DefaultInfo{Name: "admin"},
}, backend, 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.RemoteAddr = "127.0.0.1"
func() {
defer func() {
recover()
}()
handler.ServeHTTP(httptest.NewRecorder(), req)
}()
t.Logf("[%s] audit log: %v", test.desc, buf.String())
line := strings.Split(strings.TrimSpace(buf.String()), "\n")
if len(line) != len(test.expected) {
t.Errorf("[%s] Unexpected amount of lines in audit log: %d", test.desc, len(line))
continue
}
for i, re := range test.expected {
match, err := regexp.MatchString(re, line[i])
if err != nil {
t.Errorf("[%s] Unexpected error matching line %d: %v", test.desc, i, err)
continue
}
if !match {
t.Errorf("[%s] Unexpected line %d of audit: %s", test.desc, i, line[i])
}
}
}
}
func TestAuditJson(t *testing.T) {
func TestAudit(t *testing.T) {
shortRunningPath := "/api/v1/namespaces/default/pods/foo"
longRunningPath := "/api/v1/namespaces/default/pods?watch=true"
@ -480,7 +192,7 @@ func TestAuditJson(t *testing.T) {
auditID string
omitStages []auditinternal.Stage
handler func(http.ResponseWriter, *http.Request)
expected []auditv1beta1.Event
expected []auditinternal.Event
respHeader bool
}{
// short running requests with read-only verb
@ -491,7 +203,7 @@ func TestAuditJson(t *testing.T) {
"",
nil,
func(http.ResponseWriter, *http.Request) {},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "get",
@ -515,7 +227,7 @@ func TestAuditJson(t *testing.T) {
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "get",
@ -539,7 +251,7 @@ func TestAuditJson(t *testing.T) {
func(w http.ResponseWriter, req *http.Request) {
panic("kaboom")
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "get",
@ -562,7 +274,7 @@ func TestAuditJson(t *testing.T) {
"",
nil,
func(http.ResponseWriter, *http.Request) {},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "update",
@ -587,7 +299,7 @@ func TestAuditJson(t *testing.T) {
w.Write([]byte("foo"))
time.Sleep(delay)
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "update",
@ -612,7 +324,7 @@ func TestAuditJson(t *testing.T) {
w.WriteHeader(403)
w.Write([]byte("foo"))
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "update",
@ -636,7 +348,7 @@ func TestAuditJson(t *testing.T) {
func(w http.ResponseWriter, req *http.Request) {
panic("kaboom")
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "update",
@ -661,7 +373,7 @@ func TestAuditJson(t *testing.T) {
w.Write([]byte("foo"))
panic("kaboom")
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "update",
@ -684,7 +396,7 @@ func TestAuditJson(t *testing.T) {
"",
nil,
func(http.ResponseWriter, *http.Request) {},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -714,7 +426,7 @@ func TestAuditJson(t *testing.T) {
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -744,7 +456,7 @@ func TestAuditJson(t *testing.T) {
func(http.ResponseWriter, *http.Request) {
time.Sleep(delay)
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -775,7 +487,7 @@ func TestAuditJson(t *testing.T) {
time.Sleep(delay)
w.WriteHeader(403)
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -805,7 +517,7 @@ func TestAuditJson(t *testing.T) {
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -836,7 +548,7 @@ func TestAuditJson(t *testing.T) {
w.WriteHeader(403)
w.Write([]byte("foo"))
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -866,7 +578,7 @@ func TestAuditJson(t *testing.T) {
func(w http.ResponseWriter, req *http.Request) {
panic("kaboom")
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -891,7 +603,7 @@ func TestAuditJson(t *testing.T) {
w.Write([]byte("foo"))
panic("kaboom")
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageRequestReceived,
Verb: "watch",
@ -921,7 +633,7 @@ func TestAuditJson(t *testing.T) {
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StageResponseComplete,
Verb: "get",
@ -941,7 +653,7 @@ func TestAuditJson(t *testing.T) {
w.Write([]byte("foo"))
panic("kaboom")
},
[]auditv1beta1.Event{
[]auditinternal.Event{
{
Stage: auditinternal.StagePanic,
Verb: "watch",
@ -952,85 +664,74 @@ func TestAuditJson(t *testing.T) {
true,
},
} {
var buf bytes.Buffer
backend := pluginlog.NewBackend(&buf, pluginlog.FormatJson, auditv1beta1.SchemeGroupVersion)
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, test.omitStages)
handler := WithAudit(http.HandlerFunc(test.handler), &fakeRequestContextMapper{
user: &user.DefaultInfo{Name: "admin"},
}, backend, policyChecker, func(r *http.Request, ri *request.RequestInfo) bool {
// simplified long-running check
return ri.Verb == "watch"
})
t.Run(test.desc, func(t *testing.T) {
sink := &fakeAuditSink{}
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, test.omitStages)
handler := WithAudit(http.HandlerFunc(test.handler), &fakeRequestContextMapper{
user: &user.DefaultInfo{Name: "admin"},
}, 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)
if test.auditID != "" {
req.Header.Add("Audit-ID", test.auditID)
}
req.RemoteAddr = "127.0.0.1"
req, _ := http.NewRequest(test.verb, test.path, nil)
if test.auditID != "" {
req.Header.Add("Audit-ID", test.auditID)
}
req.RemoteAddr = "127.0.0.1"
w := httptest.NewRecorder()
func() {
defer func() {
recover()
func() {
defer func() {
recover()
}()
handler.ServeHTTP(httptest.NewRecorder(), req)
}()
handler.ServeHTTP(w, req)
}()
t.Logf("[%s] audit log: %v", test.desc, buf.String())
events := sink.Events()
t.Logf("audit log: %v", events)
line := strings.Split(strings.TrimSpace(buf.String()), "\n")
if len(line) != len(test.expected) {
t.Errorf("[%s] Unexpected amount of lines in audit log: %d", test.desc, len(line))
continue
}
expectedID := types.UID("")
for i, expect := range test.expected {
// decode events back to check json elements.
event := &auditv1beta1.Event{}
decoder := audit.Codecs.UniversalDecoder(auditv1beta1.SchemeGroupVersion)
if err := runtime.DecodeInto(decoder, []byte(line[i]), event); err != nil {
t.Errorf("failed decoding line %s: %v", line[i], err)
continue
}
if "admin" != event.User.Username {
t.Errorf("[%s] Unexpected username: %s", test.desc, event.User.Username)
}
if event.Stage != expect.Stage {
t.Errorf("[%s] Unexpected Stage: %s", test.desc, event.Stage)
}
if event.Verb != expect.Verb {
t.Errorf("[%s] Unexpected Verb: %s", test.desc, event.Verb)
}
if event.RequestURI != expect.RequestURI {
t.Errorf("[%s] Unexpected RequestURI: %s", test.desc, event.RequestURI)
}
resp := w.Result()
if test.respHeader && string(event.AuditID) != resp.Header.Get("Audit-Id") {
t.Errorf("[%s] Unexpected Audit-Id http response header, Audit-Id http response header should be the same with AuditID in log %v xx %v", test.desc, event.AuditID, w.HeaderMap.Get("Audit-Id"))
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("[%s] Unexpected AuditID in audit event, AuditID should be the same with Audit-ID http header", test.desc)
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)
}
}
if expectedID == types.UID("") {
expectedID = event.AuditID
} else if expectedID != event.AuditID {
t.Errorf("[%s] Audits for one request should share the same AuditID, %s differs from %s", test.desc, expectedID, event.AuditID)
}
if event.ObjectRef.APIVersion != "v1" {
t.Errorf("[%s] Unexpected apiVersion: %s", test.desc, event.ObjectRef.APIVersion)
}
if event.ObjectRef.APIGroup != "" {
t.Errorf("[%s] Unexpected apiGroup: %s", test.desc, event.ObjectRef.APIGroup)
}
if (event.ResponseStatus == nil) != (expect.ResponseStatus == nil) {
t.Errorf("[%s] Unexpected ResponseStatus: %v", test.desc, event.ResponseStatus)
continue
}
if (event.ResponseStatus != nil) && (event.ResponseStatus.Code != expect.ResponseStatus.Code) {
t.Errorf("[%s] Unexpected status code : %d", test.desc, event.ResponseStatus.Code)
}
}
})
}
}
@ -1084,3 +785,71 @@ func TestAuditLevelNone(t *testing.T) {
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, &fakeRequestContextMapper{
user: &user.DefaultInfo{Name: "admin"},
}, sink, policyChecker, nil)
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
req.RemoteAddr = "127.0.0.1"
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 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 %p", test.desc, resp.Header.Get("Audit-ID"))
}
}
}
}

View File

@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
@ -28,3 +29,22 @@ filegroup(
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["backend_test.go"],
library = ":go_default_library",
deps = [
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
],
)

View File

@ -0,0 +1,175 @@
/*
Copyright 2017 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 log
import (
"bytes"
"fmt"
"reflect"
"regexp"
"testing"
"time"
"github.com/pborman/uuid"
"k8s.io/apimachinery/pkg/apimachinery/announced"
"k8s.io/apimachinery/pkg/apimachinery/registered"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/apis/audit/install"
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
"k8s.io/apiserver/pkg/audit"
)
// NOTE: Copied from webhook backend to register auditv1beta1 to scheme
var (
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
registry = registered.NewOrDie("")
)
func init() {
allGVs := []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}
registry.RegisterVersions(allGVs)
if err := registry.EnableVersions(allGVs...); err != nil {
panic(fmt.Sprintf("failed to enable version %v", allGVs))
}
install.Install(groupFactoryRegistry, registry, audit.Scheme)
}
func TestLogEventsLegacy(t *testing.T) {
for _, test := range []struct {
event *auditinternal.Event
expected string
}{
{
&auditinternal.Event{
AuditID: types.UID(uuid.NewRandom().String()),
},
`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" stage="" ip="<unknown>" method="" user="<none>" groups="<none>" as="<self>" asgroups="<lookup>" namespace="<none>" uri="" response="<deferred>"`,
},
{
&auditinternal.Event{
ResponseStatus: &metav1.Status{
Code: 200,
},
RequestURI: "/apis/rbac.authorization.k8s.io/v1/roles",
SourceIPs: []string{
"127.0.0.1",
},
Timestamp: metav1.NewTime(time.Now()),
AuditID: types.UID(uuid.NewRandom().String()),
Stage: auditinternal.StageRequestReceived,
Verb: "get",
User: auditinternal.UserInfo{
Username: "admin",
Groups: []string{
"system:masters",
"system:authenticated",
},
},
ObjectRef: &auditinternal.ObjectReference{
Namespace: "default",
},
},
`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" stage="RequestReceived" ip="127.0.0.1" method="get" user="admin" groups="\\"system:masters\\",\\"system:authenticated\\"" as="<self>" asgroups="<lookup>" namespace="default" uri="/apis/rbac.authorization.k8s.io/v1/roles" response="200"`,
},
{
&auditinternal.Event{
AuditID: types.UID(uuid.NewRandom().String()),
Level: auditinternal.LevelMetadata,
ObjectRef: &auditinternal.ObjectReference{
Resource: "foo",
APIVersion: "v1",
Subresource: "bar",
},
},
`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" stage="" ip="<unknown>" method="" user="<none>" groups="<none>" as="<self>" asgroups="<lookup>" namespace="<none>" uri="" response="<deferred>"`,
},
} {
var buf bytes.Buffer
backend := NewBackend(&buf, FormatLegacy, auditv1beta1.SchemeGroupVersion)
backend.ProcessEvents(test.event)
match, err := regexp.MatchString(test.expected, buf.String())
if err != nil {
t.Errorf("Unexpected error matching line %v", err)
continue
}
if !match {
t.Errorf("Unexpected line of audit: %s", buf.String())
}
}
}
func TestLogEventsJson(t *testing.T) {
for _, event := range []*auditinternal.Event{
{
AuditID: types.UID(uuid.NewRandom().String()),
},
{
ResponseStatus: &metav1.Status{
Code: 200,
},
RequestURI: "/apis/rbac.authorization.k8s.io/v1/roles",
SourceIPs: []string{
"127.0.0.1",
},
// When encoding to json format, the nanosecond part of timestamp is
// lost and it will become zero when we decode event back, so we rounding
// timestamp down to a multiple of second.
Timestamp: metav1.NewTime(time.Now().Truncate(time.Second)),
AuditID: types.UID(uuid.NewRandom().String()),
Stage: auditinternal.StageRequestReceived,
Verb: "get",
User: auditinternal.UserInfo{
Username: "admin",
Groups: []string{
"system:masters",
"system:authenticated",
},
},
ObjectRef: &auditinternal.ObjectReference{
Namespace: "default",
},
},
{
AuditID: types.UID(uuid.NewRandom().String()),
Level: auditinternal.LevelMetadata,
ObjectRef: &auditinternal.ObjectReference{
Resource: "foo",
APIVersion: "v1",
Subresource: "bar",
},
},
} {
var buf bytes.Buffer
backend := NewBackend(&buf, FormatJson, auditv1beta1.SchemeGroupVersion)
backend.ProcessEvents(event)
// decode events back and compare with the original one.
result := &auditinternal.Event{}
decoder := audit.Codecs.UniversalDecoder(auditv1beta1.SchemeGroupVersion)
if err := runtime.DecodeInto(decoder, buf.Bytes(), result); err != nil {
t.Errorf("failed decoding buf: %s", buf.String())
continue
}
if !reflect.DeepEqual(event, result) {
t.Errorf("The result event should be the same with the original one, \noriginal: \n%#v\n result: \n%#v", event, result)
}
}
}