-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add audit filter that will be able to catch authn failures
- Loading branch information
Showing
2 changed files
with
139 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package origin | ||
|
||
import ( | ||
"bufio" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/golang/glog" | ||
"github.com/pborman/uuid" | ||
|
||
utilnet "k8s.io/apimachinery/pkg/util/net" | ||
apifilters "k8s.io/apiserver/pkg/endpoints/filters" | ||
apiresponsewriters "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" | ||
apirequest "k8s.io/apiserver/pkg/endpoints/request" | ||
) | ||
|
||
const AUDIT_TRIGGERED = "audittriggered" | ||
|
||
var _ http.ResponseWriter = &auditResponseWriter{} | ||
|
||
type auditResponseWriter struct { | ||
http.ResponseWriter | ||
requestContextMapper apirequest.RequestContextMapper | ||
req *http.Request | ||
out io.Writer | ||
} | ||
|
||
func (a *auditResponseWriter) WriteHeader(code int) { | ||
ctx, ok := a.requestContextMapper.Get(a.req) | ||
if !ok { | ||
glog.Errorf("Unable to get RequestContextMapper and read context data!") | ||
return | ||
} | ||
// if the original audit handler triggered there's no need to do anything | ||
triggeredValue := ctx.Value(AUDIT_TRIGGERED) | ||
if triggered, ok := triggeredValue.(bool); ok && triggered { | ||
return | ||
} | ||
// else we log simulating original audit lines | ||
id := uuid.NewRandom().String() | ||
line := fmt.Sprintf("%s AUDIT: id=%q ip=%q method=%q user=<???> uri=%q\n", | ||
time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(a.req), a.req.Method, a.req.URL) | ||
if _, err := fmt.Fprint(a.out, line); err != nil { | ||
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err) | ||
} | ||
line = fmt.Sprintf("%s AUDIT: id=%q response=\"%d\"\n", time.Now().Format(time.RFC3339Nano), id, code) | ||
if _, err := fmt.Fprint(a.out, line); err != nil { | ||
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err) | ||
} | ||
|
||
a.ResponseWriter.WriteHeader(code) | ||
} | ||
|
||
// fancyResponseWriterDelegator implements http.CloseNotifier, http.Flusher and | ||
// http.Hijacker which are needed to make certain http operation (e.g. watch, rsh, etc) | ||
// working. | ||
type fancyResponseWriterDelegator struct { | ||
*auditResponseWriter | ||
} | ||
|
||
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool { | ||
return f.ResponseWriter.(http.CloseNotifier).CloseNotify() | ||
} | ||
|
||
func (f *fancyResponseWriterDelegator) Flush() { | ||
f.ResponseWriter.(http.Flusher).Flush() | ||
} | ||
|
||
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||
return f.ResponseWriter.(http.Hijacker).Hijack() | ||
} | ||
|
||
var _ http.CloseNotifier = &fancyResponseWriterDelegator{} | ||
var _ http.Flusher = &fancyResponseWriterDelegator{} | ||
var _ http.Hijacker = &fancyResponseWriterDelegator{} | ||
|
||
// withAuditWrapper decorates a http.Handler with audit logging information for all the | ||
// requests coming to the server. If out is nil, no decoration takes place. | ||
func withAuditWrapper(handler http.Handler, requestContextMapper apirequest.RequestContextMapper, out io.Writer) http.Handler { | ||
if out == nil { | ||
return handler | ||
} | ||
// enable the original audit handler | ||
handler = apifilters.WithAudit(handler, requestContextMapper, out) | ||
// and wrap it with the piece of code responsible for injecting flag into the | ||
// context which will tell the AuthnAudit handler if he should or not log | ||
// information about failed Authn operation. | ||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
ctx, ok := requestContextMapper.Get(req) | ||
if !ok { | ||
apiresponsewriters.InternalError(w, req, errors.New("no context found for request")) | ||
return | ||
} | ||
apirequest.WithValue(ctx, AUDIT_TRIGGERED, true) | ||
handler.ServeHTTP(w, req) | ||
}) | ||
} | ||
|
||
// withAuthnAudit decorates a http.Handler with audit logging ONLY information | ||
// related to failed Authn requests. | ||
func withAuthnAudit(handler http.Handler, requestContextMapper apirequest.RequestContextMapper, out io.Writer) http.Handler { | ||
if out == nil { | ||
return handler | ||
} | ||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
respWriter := decorateResponseWriter(w, req, requestContextMapper, out) | ||
handler.ServeHTTP(respWriter, req) | ||
}) | ||
} | ||
|
||
func decorateResponseWriter(responseWriter http.ResponseWriter, req *http.Request, | ||
requestContextMapper apirequest.RequestContextMapper, out io.Writer) http.ResponseWriter { | ||
delegate := &auditResponseWriter{ | ||
ResponseWriter: responseWriter, | ||
req: req, | ||
requestContextMapper: requestContextMapper, | ||
out: out, | ||
} | ||
// check if the ResponseWriter we're wrapping is the fancy one we need | ||
// or if the basic is sufficient | ||
_, cn := responseWriter.(http.CloseNotifier) | ||
_, fl := responseWriter.(http.Flusher) | ||
_, hj := responseWriter.(http.Hijacker) | ||
if cn && fl && hj { | ||
return &fancyResponseWriterDelegator{delegate} | ||
} | ||
return delegate | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters