Return Audit-Id http header for trouble shooting

Kubernetes-commit: 4a1e7ddaa6e0d2e92ce27d9846cfc8407e1fcb60
This commit is contained in:
Cao Shufeng 2017-08-03 18:29:19 +08:00 committed by Kubernetes Publisher
parent f43e4c3c30
commit 4ace90bfb4
4 changed files with 70 additions and 12 deletions

View File

@ -27,6 +27,13 @@ const (
// Header to hold the audit ID as the request is propagated through the serving hierarchy. The
// Audit-ID header should be set by the first server to receive the request (e.g. the federation
// server or kube-aggregator).
//
// Audit ID is also returned to client by http response header.
// It's not guaranteed Audit-Id http header is sent for all requests. When kube-apiserver didn't
// audit the events according to the audit policy, no Audit-ID is returned. Also, for request to
// pods/exec, pods/attach, pods/proxy, kube-apiserver works like a proxy and redirect the request
// to kubelet node, users will only get http headers sent from kubelet node, so no Audit-ID is
// sent when users run command like "kubectl exec" or "kubectl attach".
HeaderAuditID = "Audit-ID"
)

View File

@ -28,6 +28,13 @@ const (
// Header to hold the audit ID as the request is propagated through the serving hierarchy. The
// Audit-ID header should be set by the first server to receive the request (e.g. the federation
// server or kube-aggregator).
//
// Audit ID is also returned to client by http response header.
// It's not guaranteed Audit-Id http header is sent for all requests. When kube-apiserver didn't
// audit the events according to the audit policy, no Audit-ID is returned. Also, for request to
// pods/exec, pods/attach, pods/proxy, kube-apiserver works like a proxy and redirect the request
// to kubelet node, users will only get http headers sent from kubelet node, so no Audit-ID is
// sent when users run command like "kubectl exec" or "kubectl attach".
HeaderAuditID = "Audit-ID"
)

View File

@ -107,6 +107,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
}
// if no StageResponseStarted event was sent b/c neither a status code nor a body was sent, fake it here
// But Audit-Id http header will only be sent when http.ResponseWriter.WriteHeader is called.
fakedSuccessStatus := &metav1.Status{
Code: http.StatusOK,
Status: metav1.StatusSuccess,
@ -162,6 +163,10 @@ type auditResponseWriter struct {
sink audit.Sink
}
func (a *auditResponseWriter) setHttpHeader() {
a.ResponseWriter.Header().Set(auditinternal.HeaderAuditID, string(a.event.AuditID))
}
func (a *auditResponseWriter) processCode(code int) {
a.once.Do(func() {
if a.event.ResponseStatus == nil {
@ -177,12 +182,16 @@ func (a *auditResponseWriter) processCode(code int) {
}
func (a *auditResponseWriter) Write(bs []byte) (int, error) {
a.processCode(http.StatusOK) // the Go library calls WriteHeader internally if no code was written yet. But this will go unnoticed for us
// the Go library calls WriteHeader internally if no code was written yet. But this will go unnoticed for us
a.processCode(http.StatusOK)
a.setHttpHeader()
return a.ResponseWriter.Write(bs)
}
func (a *auditResponseWriter) WriteHeader(code int) {
a.processCode(code)
a.setHttpHeader()
a.ResponseWriter.WriteHeader(code)
}
@ -204,6 +213,13 @@ func (f *fancyResponseWriterDelegator) Flush() {
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
// fake a response status before protocol switch happens
f.processCode(http.StatusSwitchingProtocols)
// This will be ignored if WriteHeader() function has aready been called.
// It's not guaranteed Audit-ID http header is sent for all requests.
// For example, when user run "kubectl exec", apiserver uses a proxy handler
// to deal with the request, users can only get http headers returned by kubelet node.
f.setHttpHeader()
return f.ResponseWriter.(http.Hijacker).Hijack()
}

View File

@ -436,12 +436,13 @@ func TestAuditJson(t *testing.T) {
delay := 500 * time.Millisecond
for _, test := range []struct {
desc string
path string
verb string
auditID string
handler func(http.ResponseWriter, *http.Request)
expected []auditv1alpha1.Event
desc string
path string
verb string
auditID string
handler func(http.ResponseWriter, *http.Request)
expected []auditv1alpha1.Event
respHeader bool
}{
// short running requests with read-only verb
{
@ -463,13 +464,16 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
false,
},
{
"short running with auditID",
shortRunningPath,
"GET",
uuid.NewRandom().String(),
func(http.ResponseWriter, *http.Request) {},
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
},
[]auditv1alpha1.Event{
{
Stage: auditinternal.StageRequestReceived,
@ -483,6 +487,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
true,
},
{
"read-only panic",
@ -505,6 +510,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 500},
},
},
false,
},
// short running request with non-read-only verb
{
@ -526,13 +532,15 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
false,
},
{
"writing sleep",
shortRunningPath,
"PUT",
"",
func(http.ResponseWriter, *http.Request) {
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
time.Sleep(delay)
},
[]auditv1alpha1.Event{
@ -548,6 +556,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
true,
},
{
"writing 403+write",
@ -571,6 +580,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 403},
},
},
true,
},
{
"writing panic",
@ -593,6 +603,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 500},
},
},
false,
},
{
"writing write+panic",
@ -616,6 +627,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 500},
},
},
true,
},
// long running requests
{
@ -643,13 +655,16 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
false,
},
{
"empty longrunning",
"empty longrunning with audit id",
longRunningPath,
"GET",
uuid.NewRandom().String(),
func(http.ResponseWriter, *http.Request) {},
func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("foo"))
},
[]auditv1alpha1.Event{
{
Stage: auditinternal.StageRequestReceived,
@ -669,6 +684,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
true,
},
{
"sleep longrunning",
@ -697,6 +713,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
false,
},
{
"sleep+403 longrunning",
@ -726,6 +743,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 403},
},
},
true,
},
{
"write longrunning",
@ -754,6 +772,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 200},
},
},
true,
},
{
"403+write longrunning",
@ -783,6 +802,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 403},
},
},
true,
},
{
"panic longrunning",
@ -805,6 +825,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 500},
},
},
false,
},
{
"write+panic longrunning",
@ -834,6 +855,7 @@ func TestAuditJson(t *testing.T) {
ResponseStatus: &metav1.Status{Code: 500},
},
},
true,
},
} {
var buf bytes.Buffer
@ -852,11 +874,12 @@ func TestAuditJson(t *testing.T) {
}
req.RemoteAddr = "127.0.0.1"
w := httptest.NewRecorder()
func() {
defer func() {
recover()
}()
handler.ServeHTTP(httptest.NewRecorder(), req)
handler.ServeHTTP(w, req)
}()
t.Logf("[%s] audit log: %v", test.desc, buf.String())
@ -887,6 +910,11 @@ func TestAuditJson(t *testing.T) {
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 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)
}