mirror of https://github.com/k3s-io/k3s
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 backendpull/6/head
parent
8884f984e4
commit
c030026b54
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -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,12 +664,12 @@ func TestAuditJson(t *testing.T) {
|
|||
true,
|
||||
},
|
||||
} {
|
||||
var buf bytes.Buffer
|
||||
backend := pluginlog.NewBackend(&buf, pluginlog.FormatJson, auditv1beta1.SchemeGroupVersion)
|
||||
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"},
|
||||
}, backend, policyChecker, func(r *http.Request, ri *request.RequestInfo) bool {
|
||||
}, sink, policyChecker, func(r *http.Request, ri *request.RequestInfo) bool {
|
||||
// simplified long-running check
|
||||
return ri.Verb == "watch"
|
||||
})
|
||||
|
@ -968,69 +680,58 @@ func TestAuditJson(t *testing.T) {
|
|||
}
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
func() {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
handler.ServeHTTP(w, req)
|
||||
handler.ServeHTTP(httptest.NewRecorder(), 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
|
||||
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 {
|
||||
// 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
|
||||
}
|
||||
event := events[i]
|
||||
if "admin" != event.User.Username {
|
||||
t.Errorf("[%s] Unexpected username: %s", test.desc, event.User.Username)
|
||||
t.Errorf("Unexpected username: %s", event.User.Username)
|
||||
}
|
||||
if event.Stage != expect.Stage {
|
||||
t.Errorf("[%s] Unexpected Stage: %s", test.desc, event.Stage)
|
||||
t.Errorf("Unexpected Stage: %s", event.Stage)
|
||||
}
|
||||
if event.Verb != expect.Verb {
|
||||
t.Errorf("[%s] Unexpected Verb: %s", test.desc, event.Verb)
|
||||
t.Errorf("Unexpected Verb: %s", 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"))
|
||||
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)
|
||||
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("[%s] Audits for one request should share the same AuditID, %s differs from %s", test.desc, 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("[%s] Unexpected apiVersion: %s", test.desc, event.ObjectRef.APIVersion)
|
||||
t.Errorf("Unexpected apiVersion: %s", event.ObjectRef.APIVersion)
|
||||
}
|
||||
if event.ObjectRef.APIGroup != "" {
|
||||
t.Errorf("[%s] Unexpected apiGroup: %s", test.desc, event.ObjectRef.APIGroup)
|
||||
t.Errorf("Unexpected apiGroup: %s", event.ObjectRef.APIGroup)
|
||||
}
|
||||
if (event.ResponseStatus == nil) != (expect.ResponseStatus == nil) {
|
||||
t.Errorf("[%s] Unexpected ResponseStatus: %v", test.desc, event.ResponseStatus)
|
||||
t.Errorf("Unexpected ResponseStatus: %v", 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)
|
||||
t.Errorf("Unexpected status code : %d", 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue