blob: 4946341078961e93023b3ae44f87f9bb2914d852 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package filters
18
19import (
20 "bufio"
21 "errors"
22 "fmt"
23 "net"
24 "net/http"
25 "sync"
26 "time"
27
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
30 auditinternal "k8s.io/apiserver/pkg/apis/audit"
31 "k8s.io/apiserver/pkg/audit"
32 "k8s.io/apiserver/pkg/audit/policy"
33 "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
34 "k8s.io/apiserver/pkg/endpoints/request"
35)
36
37// WithAudit decorates a http.Handler with audit logging information for all the
38// requests coming to the server. Audit level is decided according to requests'
39// attributes and audit policy. Logs are emitted to the audit sink to
40// process events. If sink or audit policy is nil, no decoration takes place.
41func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker, longRunningCheck request.LongRunningRequestCheck) http.Handler {
42 if sink == nil || policy == nil {
43 return handler
44 }
45 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
46 req, ev, omitStages, err := createAuditEventAndAttachToContext(req, policy)
47 if err != nil {
48 utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
49 responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
50 return
51 }
52 ctx := req.Context()
53 if ev == nil || ctx == nil {
54 handler.ServeHTTP(w, req)
55 return
56 }
57
58 ev.Stage = auditinternal.StageRequestReceived
59 processAuditEvent(sink, ev, omitStages)
60
61 // intercept the status code
62 var longRunningSink audit.Sink
63 if longRunningCheck != nil {
64 ri, _ := request.RequestInfoFrom(ctx)
65 if longRunningCheck(req, ri) {
66 longRunningSink = sink
67 }
68 }
69 respWriter := decorateResponseWriter(w, ev, longRunningSink, omitStages)
70
71 // send audit event when we leave this func, either via a panic or cleanly. In the case of long
72 // running requests, this will be the second audit event.
73 defer func() {
74 if r := recover(); r != nil {
75 defer panic(r)
76 ev.Stage = auditinternal.StagePanic
77 ev.ResponseStatus = &metav1.Status{
78 Code: http.StatusInternalServerError,
79 Status: metav1.StatusFailure,
80 Reason: metav1.StatusReasonInternalError,
81 Message: fmt.Sprintf("APIServer panic'd: %v", r),
82 }
83 processAuditEvent(sink, ev, omitStages)
84 return
85 }
86
87 // if no StageResponseStarted event was sent b/c neither a status code nor a body was sent, fake it here
88 // But Audit-Id http header will only be sent when http.ResponseWriter.WriteHeader is called.
89 fakedSuccessStatus := &metav1.Status{
90 Code: http.StatusOK,
91 Status: metav1.StatusSuccess,
92 Message: "Connection closed early",
93 }
94 if ev.ResponseStatus == nil && longRunningSink != nil {
95 ev.ResponseStatus = fakedSuccessStatus
96 ev.Stage = auditinternal.StageResponseStarted
97 processAuditEvent(longRunningSink, ev, omitStages)
98 }
99
100 ev.Stage = auditinternal.StageResponseComplete
101 if ev.ResponseStatus == nil {
102 ev.ResponseStatus = fakedSuccessStatus
103 }
104 processAuditEvent(sink, ev, omitStages)
105 }()
106 handler.ServeHTTP(respWriter, req)
107 })
108}
109
110// createAuditEventAndAttachToContext is responsible for creating the audit event
111// and attaching it to the appropriate request context. It returns:
112// - context with audit event attached to it
113// - created audit event
114// - error if anything bad happened
115func createAuditEventAndAttachToContext(req *http.Request, policy policy.Checker) (*http.Request, *auditinternal.Event, []auditinternal.Stage, error) {
116 ctx := req.Context()
117
118 attribs, err := GetAuthorizerAttributes(ctx)
119 if err != nil {
120 return req, nil, nil, fmt.Errorf("failed to GetAuthorizerAttributes: %v", err)
121 }
122
123 level, omitStages := policy.LevelAndStages(attribs)
124 audit.ObservePolicyLevel(level)
125 if level == auditinternal.LevelNone {
126 // Don't audit.
127 return req, nil, nil, nil
128 }
129
130 ev, err := audit.NewEventFromRequest(req, level, attribs)
131 if err != nil {
132 return req, nil, nil, fmt.Errorf("failed to complete audit event from request: %v", err)
133 }
134
135 req = req.WithContext(request.WithAuditEvent(ctx, ev))
136
137 return req, ev, omitStages, nil
138}
139
140func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) {
141 for _, stage := range omitStages {
142 if ev.Stage == stage {
143 return
144 }
145 }
146
147 if ev.Stage == auditinternal.StageRequestReceived {
148 ev.StageTimestamp = metav1.NewMicroTime(ev.RequestReceivedTimestamp.Time)
149 } else {
150 ev.StageTimestamp = metav1.NewMicroTime(time.Now())
151 }
152 audit.ObserveEvent()
153 sink.ProcessEvents(ev)
154}
155
156func decorateResponseWriter(responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter {
157 delegate := &auditResponseWriter{
158 ResponseWriter: responseWriter,
159 event: ev,
160 sink: sink,
161 omitStages: omitStages,
162 }
163
164 // check if the ResponseWriter we're wrapping is the fancy one we need
165 // or if the basic is sufficient
166 _, cn := responseWriter.(http.CloseNotifier)
167 _, fl := responseWriter.(http.Flusher)
168 _, hj := responseWriter.(http.Hijacker)
169 if cn && fl && hj {
170 return &fancyResponseWriterDelegator{delegate}
171 }
172 return delegate
173}
174
175var _ http.ResponseWriter = &auditResponseWriter{}
176
177// auditResponseWriter intercepts WriteHeader, sets it in the event. If the sink is set, it will
178// create immediately an event (for long running requests).
179type auditResponseWriter struct {
180 http.ResponseWriter
181 event *auditinternal.Event
182 once sync.Once
183 sink audit.Sink
184 omitStages []auditinternal.Stage
185}
186
187func (a *auditResponseWriter) setHttpHeader() {
188 a.ResponseWriter.Header().Set(auditinternal.HeaderAuditID, string(a.event.AuditID))
189}
190
191func (a *auditResponseWriter) processCode(code int) {
192 a.once.Do(func() {
193 if a.event.ResponseStatus == nil {
194 a.event.ResponseStatus = &metav1.Status{}
195 }
196 a.event.ResponseStatus.Code = int32(code)
197 a.event.Stage = auditinternal.StageResponseStarted
198
199 if a.sink != nil {
200 processAuditEvent(a.sink, a.event, a.omitStages)
201 }
202 })
203}
204
205func (a *auditResponseWriter) Write(bs []byte) (int, error) {
206 // the Go library calls WriteHeader internally if no code was written yet. But this will go unnoticed for us
207 a.processCode(http.StatusOK)
208 a.setHttpHeader()
209 return a.ResponseWriter.Write(bs)
210}
211
212func (a *auditResponseWriter) WriteHeader(code int) {
213 a.processCode(code)
214 a.setHttpHeader()
215 a.ResponseWriter.WriteHeader(code)
216}
217
218// fancyResponseWriterDelegator implements http.CloseNotifier, http.Flusher and
219// http.Hijacker which are needed to make certain http operation (e.g. watch, rsh, etc)
220// working.
221type fancyResponseWriterDelegator struct {
222 *auditResponseWriter
223}
224
225func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
226 return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
227}
228
229func (f *fancyResponseWriterDelegator) Flush() {
230 f.ResponseWriter.(http.Flusher).Flush()
231}
232
233func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
234 // fake a response status before protocol switch happens
235 f.processCode(http.StatusSwitchingProtocols)
236
237 // This will be ignored if WriteHeader() function has already been called.
238 // It's not guaranteed Audit-ID http header is sent for all requests.
239 // For example, when user run "kubectl exec", apiserver uses a proxy handler
240 // to deal with the request, users can only get http headers returned by kubelet node.
241 f.setHttpHeader()
242
243 return f.ResponseWriter.(http.Hijacker).Hijack()
244}
245
246var _ http.CloseNotifier = &fancyResponseWriterDelegator{}
247var _ http.Flusher = &fancyResponseWriterDelegator{}
248var _ http.Hijacker = &fancyResponseWriterDelegator{}