blob: bdf13c58e9124498fdec0fc44002f27f2b7c7221 [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 "fmt"
22 "io"
23 "net"
24 "net/http"
25 "strings"
26 "time"
27
28 "github.com/golang/glog"
29 "github.com/pborman/uuid"
30
31 authenticationapi "k8s.io/api/authentication/v1"
32 utilnet "k8s.io/apimachinery/pkg/util/net"
33 "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
34)
35
36var _ http.ResponseWriter = &legacyAuditResponseWriter{}
37
38type legacyAuditResponseWriter struct {
39 http.ResponseWriter
40 out io.Writer
41 id string
42}
43
44func (a *legacyAuditResponseWriter) printResponse(code int) {
45 line := fmt.Sprintf("%s AUDIT: id=%q response=\"%d\"\n", time.Now().Format(time.RFC3339Nano), a.id, code)
46 if _, err := fmt.Fprint(a.out, line); err != nil {
47 glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
48 }
49}
50
51func (a *legacyAuditResponseWriter) WriteHeader(code int) {
52 a.printResponse(code)
53 a.ResponseWriter.WriteHeader(code)
54}
55
56// fancyLegacyResponseWriterDelegator implements http.CloseNotifier, http.Flusher and
57// http.Hijacker which are needed to make certain http operation (e.g. watch, rsh, etc)
58// working.
59type fancyLegacyResponseWriterDelegator struct {
60 *legacyAuditResponseWriter
61}
62
63func (f *fancyLegacyResponseWriterDelegator) CloseNotify() <-chan bool {
64 return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
65}
66
67func (f *fancyLegacyResponseWriterDelegator) Flush() {
68 f.ResponseWriter.(http.Flusher).Flush()
69}
70
71func (f *fancyLegacyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
72 // fake a response status before protocol switch happens
73 f.printResponse(http.StatusSwitchingProtocols)
74 return f.ResponseWriter.(http.Hijacker).Hijack()
75}
76
77var _ http.CloseNotifier = &fancyLegacyResponseWriterDelegator{}
78var _ http.Flusher = &fancyLegacyResponseWriterDelegator{}
79var _ http.Hijacker = &fancyLegacyResponseWriterDelegator{}
80
81// WithLegacyAudit 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// Each audit log contains two entries:
84// 1. the request line containing:
85// - unique id allowing to match the response line (see 2)
86// - source ip of the request
87// - HTTP method being invoked
88// - original user invoking the operation
89// - original user's groups info
90// - impersonated user for the operation
91// - impersonated groups info
92// - namespace of the request or <none>
93// - uri is the full URI as requested
94// 2. the response line containing:
95// - the unique id from 1
96// - response code
97func WithLegacyAudit(handler http.Handler, out io.Writer) http.Handler {
98 if out == nil {
99 return handler
100 }
101 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
102 ctx := req.Context()
103 attribs, err := GetAuthorizerAttributes(ctx)
104 if err != nil {
105 responsewriters.InternalError(w, req, err)
106 return
107 }
108
109 username := "<none>"
110 groups := "<none>"
111 if attribs.GetUser() != nil {
112 username = attribs.GetUser().GetName()
113 if userGroups := attribs.GetUser().GetGroups(); len(userGroups) > 0 {
114 groups = auditStringSlice(userGroups)
115 }
116 }
117 asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader)
118 if len(asuser) == 0 {
119 asuser = "<self>"
120 }
121 asgroups := "<lookup>"
122 requestedGroups := req.Header[authenticationapi.ImpersonateGroupHeader]
123 if len(requestedGroups) > 0 {
124 asgroups = auditStringSlice(requestedGroups)
125 }
126 namespace := attribs.GetNamespace()
127 if len(namespace) == 0 {
128 namespace = "<none>"
129 }
130 id := uuid.NewRandom().String()
131
132 line := fmt.Sprintf("%s AUDIT: id=%q ip=%q method=%q user=%q groups=%q as=%q asgroups=%q namespace=%q uri=%q\n",
133 time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, username, groups, asuser, asgroups, namespace, req.URL)
134 if _, err := fmt.Fprint(out, line); err != nil {
135 glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
136 }
137 respWriter := legacyDecorateResponseWriter(w, out, id)
138 handler.ServeHTTP(respWriter, req)
139 })
140}
141
142func auditStringSlice(inList []string) string {
143 quotedElements := make([]string, len(inList))
144 for i, in := range inList {
145 quotedElements[i] = fmt.Sprintf("%q", in)
146 }
147 return strings.Join(quotedElements, ",")
148}
149
150func legacyDecorateResponseWriter(responseWriter http.ResponseWriter, out io.Writer, id string) http.ResponseWriter {
151 delegate := &legacyAuditResponseWriter{ResponseWriter: responseWriter, out: out, id: id}
152 // check if the ResponseWriter we're wrapping is the fancy one we need
153 // or if the basic is sufficient
154 _, cn := responseWriter.(http.CloseNotifier)
155 _, fl := responseWriter.(http.Flusher)
156 _, hj := responseWriter.(http.Hijacker)
157 if cn && fl && hj {
158 return &fancyLegacyResponseWriterDelegator{delegate}
159 }
160 return delegate
161}