Skip to content

Commit d8e1136

Browse files
committed
Add audit filter that will be able to catch authn failures
1 parent bb745a7 commit d8e1136

File tree

2 files changed

+140
-2
lines changed

2 files changed

+140
-2
lines changed

pkg/cmd/server/origin/audit.go

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package origin
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"net"
9+
"net/http"
10+
"time"
11+
12+
"github.com/golang/glog"
13+
"github.com/pborman/uuid"
14+
15+
utilnet "k8s.io/apimachinery/pkg/util/net"
16+
apifilters "k8s.io/apiserver/pkg/endpoints/filters"
17+
apiresponsewriters "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
18+
apirequest "k8s.io/apiserver/pkg/endpoints/request"
19+
)
20+
21+
const AUDIT_TRIGGERED = "audittriggered"
22+
23+
var _ http.ResponseWriter = &auditResponseWriter{}
24+
25+
type auditResponseWriter struct {
26+
http.ResponseWriter
27+
requestContextMapper apirequest.RequestContextMapper
28+
req *http.Request
29+
out io.Writer
30+
}
31+
32+
func (a *auditResponseWriter) WriteHeader(code int) {
33+
ctx, ok := a.requestContextMapper.Get(a.req)
34+
if !ok {
35+
glog.Errorf("Unable to get RequestContextMapper and read context data!")
36+
return
37+
}
38+
// if the original audit handler triggered there's no need to do anything
39+
triggeredValue := ctx.Value(AUDIT_TRIGGERED)
40+
if triggered, ok := triggeredValue.(bool); ok && triggered {
41+
return
42+
}
43+
// else we log simulating original audit lines
44+
id := uuid.NewRandom().String()
45+
line := fmt.Sprintf("%s AUDIT: id=%q ip=%q method=%q user=<???> uri=%q\n",
46+
time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(a.req), a.req.Method, a.req.URL)
47+
if _, err := fmt.Fprint(a.out, line); err != nil {
48+
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
49+
}
50+
line = fmt.Sprintf("%s AUDIT: id=%q response=\"%d\"\n", time.Now().Format(time.RFC3339Nano), id, code)
51+
if _, err := fmt.Fprint(a.out, line); err != nil {
52+
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
53+
}
54+
55+
a.ResponseWriter.WriteHeader(code)
56+
}
57+
58+
// fancyResponseWriterDelegator implements http.CloseNotifier, http.Flusher and
59+
// http.Hijacker which are needed to make certain http operation (e.g. watch, rsh, etc)
60+
// working.
61+
type fancyResponseWriterDelegator struct {
62+
*auditResponseWriter
63+
}
64+
65+
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
66+
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
67+
}
68+
69+
func (f *fancyResponseWriterDelegator) Flush() {
70+
f.ResponseWriter.(http.Flusher).Flush()
71+
}
72+
73+
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
74+
return f.ResponseWriter.(http.Hijacker).Hijack()
75+
}
76+
77+
var _ http.CloseNotifier = &fancyResponseWriterDelegator{}
78+
var _ http.Flusher = &fancyResponseWriterDelegator{}
79+
var _ http.Hijacker = &fancyResponseWriterDelegator{}
80+
81+
// withAuditWrapper decorates a http.Handler with audit logging information for all the
82+
// requests coming to the server. If out is nil, no decoration takes place.
83+
func withAuditWrapper(handler http.Handler, requestContextMapper apirequest.RequestContextMapper, out io.Writer) http.Handler {
84+
if out == nil {
85+
return handler
86+
}
87+
// enable the original audit handler
88+
handler = apifilters.WithAudit(handler, requestContextMapper, out)
89+
// and wrap it with the piece of code responsible for injecting flag into the
90+
// context which will tell the AuthnAudit handler if he should or not log
91+
// information about failed Authn operation.
92+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
93+
ctx, ok := requestContextMapper.Get(req)
94+
if !ok {
95+
apiresponsewriters.InternalError(w, req, errors.New("no context found for request"))
96+
return
97+
}
98+
ctx = apirequest.WithValue(ctx, AUDIT_TRIGGERED, true)
99+
requestContextMapper.Update(req, ctx)
100+
handler.ServeHTTP(w, req)
101+
})
102+
}
103+
104+
// withAuthnAudit decorates a http.Handler with audit logging ONLY information
105+
// related to failed Authn requests.
106+
func withAuthnAudit(handler http.Handler, requestContextMapper apirequest.RequestContextMapper, out io.Writer) http.Handler {
107+
if out == nil {
108+
return handler
109+
}
110+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
111+
respWriter := decorateResponseWriter(w, req, requestContextMapper, out)
112+
handler.ServeHTTP(respWriter, req)
113+
})
114+
}
115+
116+
func decorateResponseWriter(responseWriter http.ResponseWriter, req *http.Request,
117+
requestContextMapper apirequest.RequestContextMapper, out io.Writer) http.ResponseWriter {
118+
delegate := &auditResponseWriter{
119+
ResponseWriter: responseWriter,
120+
req: req,
121+
requestContextMapper: requestContextMapper,
122+
out: out,
123+
}
124+
// check if the ResponseWriter we're wrapping is the fancy one we need
125+
// or if the basic is sufficient
126+
_, cn := responseWriter.(http.CloseNotifier)
127+
_, fl := responseWriter.(http.Flusher)
128+
_, hj := responseWriter.(http.Hijacker)
129+
if cn && fl && hj {
130+
return &fancyResponseWriterDelegator{delegate}
131+
}
132+
return delegate
133+
}

pkg/cmd/server/origin/master.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ func (c *MasterConfig) buildHandlerChain(assetConfig *AssetConfig) (func(http.Ha
186186
handler = serverhandlers.ImpersonationFilter(handler, c.Authorizer, c.GroupCache, contextMapper)
187187

188188
// audit handler must comes before the impersonationFilter to read the original user
189+
var writer io.Writer
189190
if c.Options.AuditConfig.Enabled {
190-
var writer io.Writer
191191
if len(c.Options.AuditConfig.AuditFilePath) > 0 {
192192
writer = &lumberjack.Logger{
193193
Filename: c.Options.AuditConfig.AuditFilePath,
@@ -199,8 +199,9 @@ func (c *MasterConfig) buildHandlerChain(assetConfig *AssetConfig) (func(http.Ha
199199
// backwards compatible writer to regular log
200200
writer = cmdutil.NewGLogWriterV(0)
201201
}
202-
handler = apifilters.WithAudit(handler, contextMapper, writer)
202+
handler = withAuditWrapper(handler, contextMapper, writer)
203203
}
204+
204205
handler = serverhandlers.AuthenticationHandlerFilter(handler, c.Authenticator, contextMapper)
205206
handler = namespacingFilter(handler, contextMapper)
206207
handler = cacheControlFilter(handler, "no-store") // protected endpoints should not be cached
@@ -216,6 +217,10 @@ func (c *MasterConfig) buildHandlerChain(assetConfig *AssetConfig) (func(http.Ha
216217
}
217218
}
218219

220+
if c.Options.AuditConfig.Enabled {
221+
handler = withAuthnAudit(handler, contextMapper, writer)
222+
}
223+
219224
handler, err := assetConfig.WithAssets(handler)
220225
if err != nil {
221226
glog.Fatalf("Failed to setup serving of assets: %v", err)

0 commit comments

Comments
 (0)