mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
Return Audit-Id http header for trouble shooting
This commit is contained in:
parent
d15baf69e1
commit
4a1e7ddaa6
@ -27,6 +27,13 @@ const (
|
|||||||
// Header to hold the audit ID as the request is propagated through the serving hierarchy. The
|
// 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
|
// Audit-ID header should be set by the first server to receive the request (e.g. the federation
|
||||||
// server or kube-aggregator).
|
// 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"
|
HeaderAuditID = "Audit-ID"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,6 +28,13 @@ const (
|
|||||||
// Header to hold the audit ID as the request is propagated through the serving hierarchy. The
|
// 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
|
// Audit-ID header should be set by the first server to receive the request (e.g. the federation
|
||||||
// server or kube-aggregator).
|
// 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"
|
HeaderAuditID = "Audit-ID"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
// 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{
|
fakedSuccessStatus := &metav1.Status{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
Status: metav1.StatusSuccess,
|
Status: metav1.StatusSuccess,
|
||||||
@ -162,6 +163,10 @@ type auditResponseWriter struct {
|
|||||||
sink audit.Sink
|
sink audit.Sink
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *auditResponseWriter) setHttpHeader() {
|
||||||
|
a.ResponseWriter.Header().Set(auditinternal.HeaderAuditID, string(a.event.AuditID))
|
||||||
|
}
|
||||||
|
|
||||||
func (a *auditResponseWriter) processCode(code int) {
|
func (a *auditResponseWriter) processCode(code int) {
|
||||||
a.once.Do(func() {
|
a.once.Do(func() {
|
||||||
if a.event.ResponseStatus == nil {
|
if a.event.ResponseStatus == nil {
|
||||||
@ -177,12 +182,16 @@ func (a *auditResponseWriter) processCode(code int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *auditResponseWriter) Write(bs []byte) (int, error) {
|
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)
|
return a.ResponseWriter.Write(bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *auditResponseWriter) WriteHeader(code int) {
|
func (a *auditResponseWriter) WriteHeader(code int) {
|
||||||
a.processCode(code)
|
a.processCode(code)
|
||||||
|
a.setHttpHeader()
|
||||||
a.ResponseWriter.WriteHeader(code)
|
a.ResponseWriter.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,6 +213,13 @@ func (f *fancyResponseWriterDelegator) Flush() {
|
|||||||
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
// fake a response status before protocol switch happens
|
// fake a response status before protocol switch happens
|
||||||
f.processCode(http.StatusSwitchingProtocols)
|
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()
|
return f.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,6 +442,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
auditID string
|
auditID string
|
||||||
handler func(http.ResponseWriter, *http.Request)
|
handler func(http.ResponseWriter, *http.Request)
|
||||||
expected []auditv1alpha1.Event
|
expected []auditv1alpha1.Event
|
||||||
|
respHeader bool
|
||||||
}{
|
}{
|
||||||
// short running requests with read-only verb
|
// short running requests with read-only verb
|
||||||
{
|
{
|
||||||
@ -463,13 +464,16 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"short running with auditID",
|
"short running with auditID",
|
||||||
shortRunningPath,
|
shortRunningPath,
|
||||||
"GET",
|
"GET",
|
||||||
uuid.NewRandom().String(),
|
uuid.NewRandom().String(),
|
||||||
func(http.ResponseWriter, *http.Request) {},
|
func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Write([]byte("foo"))
|
||||||
|
},
|
||||||
[]auditv1alpha1.Event{
|
[]auditv1alpha1.Event{
|
||||||
{
|
{
|
||||||
Stage: auditinternal.StageRequestReceived,
|
Stage: auditinternal.StageRequestReceived,
|
||||||
@ -483,6 +487,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"read-only panic",
|
"read-only panic",
|
||||||
@ -505,6 +510,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 500},
|
ResponseStatus: &metav1.Status{Code: 500},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// short running request with non-read-only verb
|
// short running request with non-read-only verb
|
||||||
{
|
{
|
||||||
@ -526,13 +532,15 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"writing sleep",
|
"writing sleep",
|
||||||
shortRunningPath,
|
shortRunningPath,
|
||||||
"PUT",
|
"PUT",
|
||||||
"",
|
"",
|
||||||
func(http.ResponseWriter, *http.Request) {
|
func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Write([]byte("foo"))
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
},
|
},
|
||||||
[]auditv1alpha1.Event{
|
[]auditv1alpha1.Event{
|
||||||
@ -548,6 +556,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"writing 403+write",
|
"writing 403+write",
|
||||||
@ -571,6 +580,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 403},
|
ResponseStatus: &metav1.Status{Code: 403},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"writing panic",
|
"writing panic",
|
||||||
@ -593,6 +603,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 500},
|
ResponseStatus: &metav1.Status{Code: 500},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"writing write+panic",
|
"writing write+panic",
|
||||||
@ -616,6 +627,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 500},
|
ResponseStatus: &metav1.Status{Code: 500},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
// long running requests
|
// long running requests
|
||||||
{
|
{
|
||||||
@ -643,13 +655,16 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"empty longrunning",
|
"empty longrunning with audit id",
|
||||||
longRunningPath,
|
longRunningPath,
|
||||||
"GET",
|
"GET",
|
||||||
uuid.NewRandom().String(),
|
uuid.NewRandom().String(),
|
||||||
func(http.ResponseWriter, *http.Request) {},
|
func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Write([]byte("foo"))
|
||||||
|
},
|
||||||
[]auditv1alpha1.Event{
|
[]auditv1alpha1.Event{
|
||||||
{
|
{
|
||||||
Stage: auditinternal.StageRequestReceived,
|
Stage: auditinternal.StageRequestReceived,
|
||||||
@ -669,6 +684,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sleep longrunning",
|
"sleep longrunning",
|
||||||
@ -697,6 +713,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"sleep+403 longrunning",
|
"sleep+403 longrunning",
|
||||||
@ -726,6 +743,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 403},
|
ResponseStatus: &metav1.Status{Code: 403},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"write longrunning",
|
"write longrunning",
|
||||||
@ -754,6 +772,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 200},
|
ResponseStatus: &metav1.Status{Code: 200},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"403+write longrunning",
|
"403+write longrunning",
|
||||||
@ -783,6 +802,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 403},
|
ResponseStatus: &metav1.Status{Code: 403},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"panic longrunning",
|
"panic longrunning",
|
||||||
@ -805,6 +825,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 500},
|
ResponseStatus: &metav1.Status{Code: 500},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"write+panic longrunning",
|
"write+panic longrunning",
|
||||||
@ -834,6 +855,7 @@ func TestAuditJson(t *testing.T) {
|
|||||||
ResponseStatus: &metav1.Status{Code: 500},
|
ResponseStatus: &metav1.Status{Code: 500},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -852,11 +874,12 @@ func TestAuditJson(t *testing.T) {
|
|||||||
}
|
}
|
||||||
req.RemoteAddr = "127.0.0.1"
|
req.RemoteAddr = "127.0.0.1"
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
func() {
|
func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
recover()
|
recover()
|
||||||
}()
|
}()
|
||||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
handler.ServeHTTP(w, req)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t.Logf("[%s] audit log: %v", test.desc, buf.String())
|
t.Logf("[%s] audit log: %v", test.desc, buf.String())
|
||||||
@ -887,6 +910,11 @@ func TestAuditJson(t *testing.T) {
|
|||||||
if event.RequestURI != expect.RequestURI {
|
if event.RequestURI != expect.RequestURI {
|
||||||
t.Errorf("[%s] Unexpected RequestURI: %s", test.desc, event.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) {
|
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("[%s] Unexpected AuditID in audit event, AuditID should be the same with Audit-ID http header", test.desc)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user