2
2
package proxy
3
3
4
4
import (
5
+ "errors"
5
6
"net/http"
6
7
"strings"
8
+ "time"
7
9
8
10
authuser "k8s.io/apiserver/pkg/authentication/user"
9
11
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
@@ -14,14 +16,33 @@ import (
14
16
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/context"
15
17
)
16
18
19
+ const (
20
+ UserHeaderClientIPKey = "Remote-Client-IP"
21
+ )
22
+
23
+ var (
24
+ errUnauthorized = errors .New ("Unauthorized" )
25
+ errImpersonateHeader = errors .New ("Impersonate-User in header" )
26
+ errNoName = errors .New ("No name in OIDC info" )
27
+ errNoImpersonationConfig = errors .New ("No impersonation configuration in context" )
28
+
29
+ // http headers are case-insensitive
30
+ impersonateUserHeader = strings .ToLower (transport .ImpersonateUserHeader )
31
+ impersonateGroupHeader = strings .ToLower (transport .ImpersonateGroupHeader )
32
+ impersonateExtraHeader = strings .ToLower (transport .ImpersonateUserExtraHeaderPrefix )
33
+ )
34
+
17
35
func (p * Proxy ) withHandlers (handler http.Handler ) http.Handler {
18
36
// Set up proxy handlers
37
+ handler = p .withClientTimestamp (handler )
19
38
handler = p .auditor .WithRequest (handler )
20
39
handler = p .withImpersonateRequest (handler )
21
40
handler = p .withAuthenticateRequest (handler )
22
41
23
42
// Add the auditor backend as a shutdown hook
24
43
p .hooks .AddPreShutdownHook ("AuditBackend" , p .auditor .Shutdown )
44
+ // Add the metrics server as a shutdown hook
45
+ p .hooks .AddPreShutdownHook ("Metrics" , p .metrics .Shutdown )
25
46
26
47
return handler
27
48
}
@@ -31,27 +52,29 @@ func (p *Proxy) withAuthenticateRequest(handler http.Handler) http.Handler {
31
52
tokenReviewHandler := p .withTokenReview (handler )
32
53
33
54
return http .HandlerFunc (func (rw http.ResponseWriter , req * http.Request ) {
55
+ req , remoteAddr := context .RemoteAddr (req )
56
+
34
57
// Auth request and handle unauthed
35
58
info , ok , err := p .oidcRequestAuther .AuthenticateRequest (req )
36
59
if err != nil {
37
60
// Since we have failed OIDC auth, we will try a token review, if enabled.
61
+ p .metrics .IncrementOIDCAuthCount (false , remoteAddr , "" )
38
62
tokenReviewHandler .ServeHTTP (rw , req )
39
63
return
40
64
}
41
65
42
66
// Failed authorization
43
67
if ! ok {
68
+ p .metrics .IncrementOIDCAuthCount (false , remoteAddr , "" )
44
69
p .handleError (rw , req , errUnauthorized )
45
70
return
46
71
}
47
72
48
- var remoteAddr string
49
- req , remoteAddr = context .RemoteAddr (req )
50
-
51
73
klog .V (4 ).Infof ("authenticated request: %s" , remoteAddr )
52
74
53
75
// Add the user info to the request context
54
76
req = req .WithContext (genericapirequest .WithUser (req .Context (), info .User ))
77
+ p .metrics .IncrementOIDCAuthCount (true , remoteAddr , info .User .GetName ())
55
78
handler .ServeHTTP (rw , req )
56
79
})
57
80
}
@@ -89,8 +112,7 @@ func (p *Proxy) withImpersonateRequest(handler http.Handler) http.Handler {
89
112
return
90
113
}
91
114
92
- var remoteAddr string
93
- req , remoteAddr = context .RemoteAddr (req )
115
+ req , remoteAddr := context .RemoteAddr (req )
94
116
95
117
// If we have disabled impersonation we can forward the request right away
96
118
if p .config .DisableImpersonation {
@@ -163,14 +185,35 @@ func (p *Proxy) withImpersonateRequest(handler http.Handler) http.Handler {
163
185
})
164
186
}
165
187
188
+ // withClientTimestamp adds the current timestamp for the client request to the
189
+ // request contect.
190
+ func (p * Proxy ) withClientTimestamp (handler http.Handler ) http.Handler {
191
+ return http .HandlerFunc (func (rw http.ResponseWriter , req * http.Request ) {
192
+ req = context .WithClientRequestTimestamp (req )
193
+ handler .ServeHTTP (rw , req )
194
+ })
195
+ }
196
+
166
197
// newErrorHandler returns a handler failed requests.
167
- func (p * Proxy ) newErrorHandler () func (rw http.ResponseWriter , r * http.Request , err error ) {
168
- unauthedHandler := audit .NewUnauthenticatedHandler (p .auditor , func (rw http.ResponseWriter , r * http.Request ) {
169
- klog .V (2 ).Infof ("unauthenticated user request %s" , r .RemoteAddr )
198
+ func (p * Proxy ) newErrorHandler () func (rw http.ResponseWriter , req * http.Request , err error ) {
199
+
200
+ // Setup unauthed handler so that it is passed through the audit
201
+ unauthedHandler := audit .NewUnauthenticatedHandler (p .auditor , func (rw http.ResponseWriter , req * http.Request ) {
202
+ _ , remoteAddr := context .RemoteAddr (req )
203
+ klog .V (2 ).Infof ("unauthenticated user request %s" , remoteAddr )
170
204
http .Error (rw , "Unauthorized" , http .StatusUnauthorized )
171
205
})
172
206
173
- return func (rw http.ResponseWriter , r * http.Request , err error ) {
207
+ return func (rw http.ResponseWriter , req * http.Request , err error ) {
208
+ var statusCode int
209
+ req , remoteAddr := context .RemoteAddr (req )
210
+
211
+ // Update client duration metrics from error
212
+ defer func () {
213
+ clientDuration := context .ClientRequestTimestamp (req )
214
+ p .metrics .ObserveClient (statusCode , req .URL .Path , remoteAddr , time .Since (clientDuration ))
215
+ }()
216
+
174
217
if err == nil {
175
218
klog .Error ("error was called with no error" )
176
219
http .Error (rw , "" , http .StatusInternalServerError )
@@ -182,30 +225,35 @@ func (p *Proxy) newErrorHandler() func(rw http.ResponseWriter, r *http.Request,
182
225
// Failed auth
183
226
case errUnauthorized :
184
227
// If Unauthorized then error and report to audit
185
- unauthedHandler .ServeHTTP (rw , r )
228
+ statusCode = http .StatusUnauthorized
229
+ unauthedHandler .ServeHTTP (rw , req )
186
230
return
187
231
188
232
// User request with impersonation
189
233
case errImpersonateHeader :
190
- klog .V (2 ).Infof ("impersonation user request %s" , r .RemoteAddr )
234
+ statusCode = http .StatusForbidden
235
+ klog .V (2 ).Infof ("impersonation user request %s" , remoteAddr )
191
236
http .Error (rw , "Impersonation requests are disabled when using kube-oidc-proxy" , http .StatusForbidden )
192
237
return
193
238
194
239
// No name given or available in oidc request
195
240
case errNoName :
196
- klog .V (2 ).Infof ("no name available in oidc info %s" , r .RemoteAddr )
241
+ statusCode = http .StatusForbidden
242
+ klog .V (2 ).Infof ("no name available in oidc info %s" , remoteAddr )
197
243
http .Error (rw , "Username claim not available in OIDC Issuer response" , http .StatusForbidden )
198
244
return
199
245
200
246
// No impersonation configuration found in context
201
247
case errNoImpersonationConfig :
202
- klog .Errorf ("if you are seeing this, there is likely a bug in the proxy (%s): %s" , r .RemoteAddr , err )
248
+ statusCode = http .StatusInternalServerError
249
+ klog .Errorf ("if you are seeing this, there is likely a bug in the proxy (%s): %s" , remoteAddr , err )
203
250
http .Error (rw , "" , http .StatusInternalServerError )
204
251
return
205
252
206
253
// Server or unknown error
207
254
default :
208
- klog .Errorf ("unknown error (%s): %s" , r .RemoteAddr , err )
255
+ statusCode = http .StatusInternalServerError
256
+ klog .Errorf ("unknown error (%s): %s" , remoteAddr , err )
209
257
http .Error (rw , "" , http .StatusInternalServerError )
210
258
}
211
259
}
0 commit comments