22package proxy
33
44import (
5+ "errors"
56 "net/http"
67 "strings"
8+ "time"
79
810 authuser "k8s.io/apiserver/pkg/authentication/user"
911 genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
@@ -14,14 +16,33 @@ import (
1416 "github.com/jetstack/kube-oidc-proxy/pkg/proxy/context"
1517)
1618
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+
1735func (p * Proxy ) withHandlers (handler http.Handler ) http.Handler {
1836 // Set up proxy handlers
37+ handler = p .withClientTimestamp (handler )
1938 handler = p .auditor .WithRequest (handler )
2039 handler = p .withImpersonateRequest (handler )
2140 handler = p .withAuthenticateRequest (handler )
2241
2342 // Add the auditor backend as a shutdown hook
2443 p .hooks .AddPreShutdownHook ("AuditBackend" , p .auditor .Shutdown )
44+ // Add the metrics server as a shutdown hook
45+ p .hooks .AddPreShutdownHook ("Metrics" , p .metrics .Shutdown )
2546
2647 return handler
2748}
@@ -31,27 +52,29 @@ func (p *Proxy) withAuthenticateRequest(handler http.Handler) http.Handler {
3152 tokenReviewHandler := p .withTokenReview (handler )
3253
3354 return http .HandlerFunc (func (rw http.ResponseWriter , req * http.Request ) {
55+ req , remoteAddr := context .RemoteAddr (req )
56+
3457 // Auth request and handle unauthed
3558 info , ok , err := p .oidcRequestAuther .AuthenticateRequest (req )
3659 if err != nil {
3760 // Since we have failed OIDC auth, we will try a token review, if enabled.
61+ p .metrics .IncrementOIDCAuthCount (false , remoteAddr , "" )
3862 tokenReviewHandler .ServeHTTP (rw , req )
3963 return
4064 }
4165
4266 // Failed authorization
4367 if ! ok {
68+ p .metrics .IncrementOIDCAuthCount (false , remoteAddr , "" )
4469 p .handleError (rw , req , errUnauthorized )
4570 return
4671 }
4772
48- var remoteAddr string
49- req , remoteAddr = context .RemoteAddr (req )
50-
5173 klog .V (4 ).Infof ("authenticated request: %s" , remoteAddr )
5274
5375 // Add the user info to the request context
5476 req = req .WithContext (genericapirequest .WithUser (req .Context (), info .User ))
77+ p .metrics .IncrementOIDCAuthCount (true , remoteAddr , info .User .GetName ())
5578 handler .ServeHTTP (rw , req )
5679 })
5780}
@@ -89,8 +112,7 @@ func (p *Proxy) withImpersonateRequest(handler http.Handler) http.Handler {
89112 return
90113 }
91114
92- var remoteAddr string
93- req , remoteAddr = context .RemoteAddr (req )
115+ req , remoteAddr := context .RemoteAddr (req )
94116
95117 // If we have disabled impersonation we can forward the request right away
96118 if p .config .DisableImpersonation {
@@ -163,14 +185,35 @@ func (p *Proxy) withImpersonateRequest(handler http.Handler) http.Handler {
163185 })
164186}
165187
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+
166197// 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 )
170204 http .Error (rw , "Unauthorized" , http .StatusUnauthorized )
171205 })
172206
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+
174217 if err == nil {
175218 klog .Error ("error was called with no error" )
176219 http .Error (rw , "" , http .StatusInternalServerError )
@@ -182,30 +225,35 @@ func (p *Proxy) newErrorHandler() func(rw http.ResponseWriter, r *http.Request,
182225 // Failed auth
183226 case errUnauthorized :
184227 // If Unauthorized then error and report to audit
185- unauthedHandler .ServeHTTP (rw , r )
228+ statusCode = http .StatusUnauthorized
229+ unauthedHandler .ServeHTTP (rw , req )
186230 return
187231
188232 // User request with impersonation
189233 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 )
191236 http .Error (rw , "Impersonation requests are disabled when using kube-oidc-proxy" , http .StatusForbidden )
192237 return
193238
194239 // No name given or available in oidc request
195240 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 )
197243 http .Error (rw , "Username claim not available in OIDC Issuer response" , http .StatusForbidden )
198244 return
199245
200246 // No impersonation configuration found in context
201247 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 )
203250 http .Error (rw , "" , http .StatusInternalServerError )
204251 return
205252
206253 // Server or unknown error
207254 default :
208- klog .Errorf ("unknown error (%s): %s" , r .RemoteAddr , err )
255+ statusCode = http .StatusInternalServerError
256+ klog .Errorf ("unknown error (%s): %s" , remoteAddr , err )
209257 http .Error (rw , "" , http .StatusInternalServerError )
210258 }
211259 }
0 commit comments