Skip to content
This repository was archived by the owner on May 17, 2024. It is now read-only.

Commit 830c605

Browse files
committed
Adds proxy to main proxy and it's handlers
Signed-off-by: JoshVanL <[email protected]>
1 parent ff554c6 commit 830c605

File tree

4 files changed

+105
-43
lines changed

4 files changed

+105
-43
lines changed

pkg/proxy/context/context.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package context
33

44
import (
55
"net/http"
6+
"time"
67

78
"github.com/sebest/xff"
89
"k8s.io/apiserver/pkg/endpoints/request"
@@ -21,8 +22,11 @@ const (
2122
// bearerTokenKey is the context key for the bearer token.
2223
bearerTokenKey
2324

24-
// bearerTokenKey is the context key for the client address.
25+
// clientAddressKey is the context key for the client address.
2526
clientAddressKey
27+
28+
// clientRequestTimestampKey is the context key for the timestamp of a client request.
29+
clientRequestTimestampKey
2630
)
2731

2832
// WithNoImpersonation returns a copy of the request in which the noImpersonation context value is set.
@@ -58,6 +62,17 @@ func BearerToken(req *http.Request) string {
5862
return token
5963
}
6064

65+
// WithClientRequestTimestamp will add the current timestamp to the request context.
66+
func WithClientRequestTimestamp(req *http.Request) *http.Request {
67+
return req.WithContext(request.WithValue(req.Context(), clientRequestTimestampKey, time.Now()))
68+
}
69+
70+
// ClientRequestTimestampKey will return thetimestamp that the client request was received.
71+
func ClientRequestTimestamp(req *http.Request) time.Time {
72+
stamp, _ := req.Context().Value(clientRequestTimestampKey).(time.Time)
73+
return stamp
74+
}
75+
6176
// RemoteAddress will attempt to return the source client address if available
6277
// in the request context. If it is not, it will be gathered from the request
6378
// and entered into the context.

pkg/proxy/handlers.go

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
package proxy
33

44
import (
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+
1735
func (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
}

pkg/proxy/proxy.go

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
package proxy
33

44
import (
5-
"errors"
65
"fmt"
76
"net/http"
87
"net/http/httputil"
98
"net/url"
10-
"strings"
119
"time"
1210

1311
"k8s.io/apiserver/pkg/authentication/authenticator"
@@ -19,28 +17,13 @@ import (
1917
"k8s.io/klog"
2018

2119
"github.com/jetstack/kube-oidc-proxy/cmd/app/options"
20+
"github.com/jetstack/kube-oidc-proxy/pkg/metrics"
2221
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/audit"
2322
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/context"
2423
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/hooks"
2524
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/tokenreview"
2625
)
2726

28-
const (
29-
UserHeaderClientIPKey = "Remote-Client-IP"
30-
)
31-
32-
var (
33-
errUnauthorized = errors.New("Unauthorized")
34-
errImpersonateHeader = errors.New("Impersonate-User in header")
35-
errNoName = errors.New("No name in OIDC info")
36-
errNoImpersonationConfig = errors.New("No impersonation configuration in context")
37-
38-
// http headers are case-insensitive
39-
impersonateUserHeader = strings.ToLower(transport.ImpersonateUserHeader)
40-
impersonateGroupHeader = strings.ToLower(transport.ImpersonateGroupHeader)
41-
impersonateExtraHeader = strings.ToLower(transport.ImpersonateUserExtraHeaderPrefix)
42-
)
43-
4427
type Config struct {
4528
DisableImpersonation bool
4629
TokenReview bool
@@ -68,6 +51,7 @@ type Proxy struct {
6851
config *Config
6952

7053
hooks *hooks.Hooks
54+
metrics *metrics.Metrics
7155
handleError errorHandlerFn
7256
}
7357

@@ -76,6 +60,8 @@ func New(restConfig *rest.Config,
7660
auditOptions *options.AuditOptions,
7761
tokenReviewer *tokenreview.TokenReview,
7862
ssinfo *server.SecureServingInfo,
63+
hooks *hooks.Hooks,
64+
metrics *metrics.Metrics,
7965
config *Config) (*Proxy, error) {
8066

8167
// generate tokenAuther from oidc config
@@ -101,7 +87,8 @@ func New(restConfig *rest.Config,
10187

10288
return &Proxy{
10389
restConfig: restConfig,
104-
hooks: hooks.New(),
90+
hooks: hooks,
91+
metrics: metrics,
10592
tokenReviewer: tokenReviewer,
10693
secureServingInfo: ssinfo,
10794
config: config,
@@ -199,8 +186,22 @@ func (p *Proxy) RoundTrip(req *http.Request) (*http.Response, error) {
199186
// Set up impersonation request.
200187
rt := transport.NewImpersonatingRoundTripper(*conf, p.clientTransport)
201188

189+
req, remoteAddr := context.RemoteAddr(req)
190+
serverDuration := time.Now()
191+
clientDuration := context.ClientRequestTimestamp(req)
192+
202193
// Push request through round trippers to the API server.
203-
return rt.RoundTrip(req)
194+
resp, err := rt.RoundTrip(req)
195+
196+
var statusCode int
197+
if resp != nil {
198+
statusCode = resp.StatusCode
199+
}
200+
201+
p.metrics.ObserveClient(statusCode, req.URL.Path, remoteAddr, time.Since(clientDuration))
202+
p.metrics.ObserveServer(statusCode, req.URL.Path, remoteAddr, time.Since(serverDuration))
203+
204+
return resp, err
204205
}
205206

206207
func (p *Proxy) reviewToken(rw http.ResponseWriter, req *http.Request) bool {
@@ -259,7 +260,3 @@ func (p *Proxy) roundTripperForRestConfig(config *rest.Config) (http.RoundTrippe
259260
func (p *Proxy) OIDCTokenAuthenticator() authenticator.Token {
260261
return p.tokenAuther
261262
}
262-
263-
func (p *Proxy) RunPreShutdownHooks() error {
264-
return p.hooks.RunPreShutdownHooks()
265-
}

pkg/proxy/proxy_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"k8s.io/apiserver/pkg/server"
2222

2323
"github.com/jetstack/kube-oidc-proxy/cmd/app/options"
24+
"github.com/jetstack/kube-oidc-proxy/pkg/metrics"
2425
"github.com/jetstack/kube-oidc-proxy/pkg/mocks"
2526
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/audit"
2627
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/hooks"
@@ -63,6 +64,7 @@ func (f *fakeRW) Header() http.Header {
6364
func newFakeR() *http.Request {
6465
return &http.Request{
6566
RemoteAddr: "fakeAddr",
67+
URL: new(url.URL),
6668
}
6769
}
6870

@@ -117,8 +119,7 @@ func (f *fakeRT) RoundTrip(h *http.Request) (*http.Response, error) {
117119
}
118120

119121
func tryError(t *testing.T, expCode int, err error) *fakeRW {
120-
p := new(Proxy)
121-
p.handleError = p.newErrorHandler()
122+
p := newTestProxy(t)
122123

123124
frw := newFakeRW()
124125
fr := newFakeR()
@@ -169,7 +170,7 @@ func TestError(t *testing.T) {
169170
}
170171

171172
func TestHasImpersonation(t *testing.T) {
172-
p := new(Proxy)
173+
p := newTestProxy(t)
173174

174175
// no impersonation headers
175176
noImpersonation := []http.Header{
@@ -269,6 +270,7 @@ func newTestProxy(t *testing.T) *fakeProxy {
269270
noAuthClientTransport: fakeRT,
270271
config: new(Config),
271272
hooks: hooks.New(),
273+
metrics: metrics.New(),
272274
},
273275
}
274276

@@ -425,7 +427,7 @@ func TestHandlers(t *testing.T) {
425427
expAuthToken: "fake-token",
426428
authResponse: &authResponse{
427429
resp: &authenticator.Response{
428-
User: nil,
430+
User: &user.DefaultInfo{},
429431
},
430432
pass: true,
431433
err: nil,

0 commit comments

Comments
 (0)