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

Commit f04ebef

Browse files
committed
Adds auditing options and functionality to proxy
Signed-off-by: JoshVanL <[email protected]>
1 parent 597ffd5 commit f04ebef

File tree

8 files changed

+215
-8
lines changed

8 files changed

+215
-8
lines changed

cmd/app/options/audit.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package options
33

44
import (
5+
"github.com/spf13/cobra"
56
"github.com/spf13/pflag"
67
apiserveroptions "k8s.io/apiserver/pkg/server/options"
78
cliflag "k8s.io/component-base/cli/flag"
@@ -23,3 +24,11 @@ func (a *AuditOptions) AddFlags(fs *pflag.FlagSet) *AuditOptions {
2324
a.AuditOptions.AddFlags(fs)
2425
return a
2526
}
27+
28+
func (a *AuditOptions) DynamicConfigurationFlagChanged(cmd *cobra.Command) bool {
29+
if ff := cmd.Flag("audit-dynamic-configuration"); ff != nil && ff.Changed {
30+
return true
31+
}
32+
33+
return false
34+
}

cmd/app/options/options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Options struct {
1919
App *KubeOIDCProxyOptions
2020
OIDCAuthentication *OIDCAuthenticationOptions
2121
SecureServing *SecureServingOptions
22+
Audit *AuditOptions
2223
Client *ClientOptions
2324
Misc *MiscOptions
2425

@@ -33,6 +34,7 @@ func New() *Options {
3334
App: NewKubeOIDCProxyOptions(nfs),
3435
OIDCAuthentication: NewOIDCAuthenticationOptions(nfs),
3536
SecureServing: NewSecureServingOptions(nfs),
37+
Audit: NewAuditOptions(nfs),
3638
Client: NewClientOptions(nfs),
3739
Misc: NewMiscOptions(nfs),
3840

@@ -80,11 +82,19 @@ func (o *Options) Validate(cmd *cobra.Command) error {
8082
errs = append(errs, errors.New("unable to securely serve on port 8080 (used by readiness probe)"))
8183
}
8284

85+
if err := o.Audit.Validate(); len(err) > 0 {
86+
errs = append(errs, err...)
87+
}
88+
8389
if o.App.DisableImpersonation &&
8490
(o.App.ExtraHeaderOptions.EnableClientIPExtraUserHeader || len(o.App.ExtraHeaderOptions.ExtraUserHeaders) > 0) {
8591
errs = append(errs, errors.New("cannot add extra user headers when impersonation disabled"))
8692
}
8793

94+
if o.Audit.DynamicConfigurationFlagChanged(cmd) {
95+
errs = append(errs, errors.New("The flag --audit-dynamic-configuration may not be set"))
96+
}
97+
8898
if len(errs) > 0 {
8999
return k8sErrors.NewAggregate(errs)
90100
}

cmd/app/run.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,20 @@ func buildRunCommand(stopCh <-chan struct{}, opts *options.Options) *cobra.Comma
7272
return err
7373
}
7474

75-
proxyConfig := &proxy.Config{
75+
proxyOptions := &proxy.Config{
7676
TokenReview: opts.App.TokenPassthrough.Enabled,
7777
DisableImpersonation: opts.App.DisableImpersonation,
7878

79-
FlushInterval: opts.App.FlushInterval,
79+
FlushInterval: opts.App.FlushInterval,
80+
ExternalAddress: opts.SecureServing.BindAddress.String(),
8081

8182
ExtraUserHeaders: opts.App.ExtraHeaderOptions.ExtraUserHeaders,
8283
ExtraUserHeadersClientIPEnabled: opts.App.ExtraHeaderOptions.EnableClientIPExtraUserHeader,
8384
}
8485

8586
// Initialise proxy with OIDC token authenticator
86-
p, err := proxy.New(restConfig, opts.OIDCAuthentication,
87-
tokenReviewer, secureServingInfo, proxyConfig)
87+
p, err := proxy.New(restConfig, opts.OIDCAuthentication, opts.Audit,
88+
tokenReviewer, secureServingInfo, proxyOptions)
8889
if err != nil {
8990
return err
9091
}
@@ -109,6 +110,10 @@ func buildRunCommand(stopCh <-chan struct{}, opts *options.Options) *cobra.Comma
109110

110111
<-waitCh
111112

113+
if err := p.RunShutdownHooks(); err != nil {
114+
return err
115+
}
116+
112117
return nil
113118
},
114119
}

pkg/proxy/audit/audit.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright Jetstack Ltd. See LICENSE for details.
2+
package audit
3+
4+
import (
5+
"fmt"
6+
"net/http"
7+
8+
"k8s.io/apimachinery/pkg/util/sets"
9+
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
10+
"k8s.io/apiserver/pkg/server"
11+
genericfilters "k8s.io/apiserver/pkg/server/filters"
12+
13+
"github.com/jetstack/kube-oidc-proxy/cmd/app/options"
14+
)
15+
16+
type Audit struct {
17+
options *options.AuditOptions
18+
serverConfig *server.CompletedConfig
19+
}
20+
21+
func New(options *options.AuditOptions, externalAddress string, secureServingInfo *server.SecureServingInfo) (*Audit, error) {
22+
serverConfig := &server.Config{
23+
ExternalAddress: externalAddress,
24+
SecureServing: secureServingInfo,
25+
26+
// Default to treating watch as a long-running operation
27+
// Generic API servers have no inherent long-running subresources
28+
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(
29+
sets.NewString("watch"), sets.NewString()),
30+
}
31+
32+
// We do not support dynamic auditing, so leave nil
33+
if err := options.ApplyTo(serverConfig, nil, nil, nil, nil); err != nil {
34+
return nil, err
35+
}
36+
37+
completed := serverConfig.Complete(nil)
38+
39+
return &Audit{
40+
options: options,
41+
serverConfig: &completed,
42+
}, nil
43+
}
44+
45+
func (a *Audit) Run(stopCh <-chan struct{}) error {
46+
if a.serverConfig.AuditBackend != nil {
47+
if err := a.serverConfig.AuditBackend.Run(stopCh); err != nil {
48+
return fmt.Errorf("failed to run the audit backend: %s", err)
49+
}
50+
}
51+
52+
return nil
53+
}
54+
55+
func (a *Audit) Shutdown() error {
56+
if a.serverConfig.AuditBackend != nil {
57+
a.serverConfig.AuditBackend.Shutdown()
58+
}
59+
60+
return nil
61+
}
62+
63+
func (a *Audit) WithRequest(handler http.Handler) http.Handler {
64+
handler = genericapifilters.WithAudit(handler, a.serverConfig.AuditBackend, a.serverConfig.AuditPolicyChecker, a.serverConfig.LongRunningFunc)
65+
return genericapifilters.WithRequestInfo(handler, a.serverConfig.RequestInfoResolver)
66+
}
67+
68+
func (a *Audit) WithUnauthorized(handler http.Handler) http.Handler {
69+
handler = genericapifilters.WithFailedAuthenticationAudit(handler, a.serverConfig.AuditBackend, a.serverConfig.AuditPolicyChecker)
70+
return genericapifilters.WithRequestInfo(handler, a.serverConfig.RequestInfoResolver)
71+
}

pkg/proxy/audit/handler.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright Jetstack Ltd. See LICENSE for details.
2+
package audit
3+
4+
import (
5+
"net/http"
6+
)
7+
8+
// This struct is used to implement an http.Handler interface. This will not
9+
// actually serve but instead implements auditing during unauthenticated
10+
// requests. It is expected that consumers of this type will call `ServeHTTP`
11+
// when an unauthenticated request is received.
12+
type unauthenticatedHandler struct {
13+
serveFunc func(http.ResponseWriter, *http.Request)
14+
}
15+
16+
func NewUnauthenticatedHandler(a *Audit, serveFunc func(http.ResponseWriter, *http.Request)) http.Handler {
17+
u := &unauthenticatedHandler{
18+
serveFunc: serveFunc,
19+
}
20+
21+
// if auditor is nil then return without wrapping
22+
if a == nil {
23+
return u
24+
}
25+
26+
return a.WithUnauthorized(u)
27+
}
28+
29+
func (u *unauthenticatedHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
30+
u.serveFunc(rw, r)
31+
}

pkg/proxy/handlers.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@ import (
1010
"k8s.io/client-go/transport"
1111
"k8s.io/klog"
1212

13+
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/audit"
1314
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/context"
1415
)
1516

1617
func (p *Proxy) withHandlers(handler http.Handler) http.Handler {
1718
// Set up proxy handlers
19+
handler = p.auditor.WithRequest(handler)
1820
handler = p.withImpersonateRequest(handler)
1921
handler = p.withAuthenticateRequest(handler)
22+
23+
// Add the auditor backend as a shutdown hook
24+
p.hooks.AddPreShutdownHook("AuditBackend", p.auditor.Shutdown)
25+
2026
return handler
2127
}
2228

@@ -159,6 +165,11 @@ func (p *Proxy) withImpersonateRequest(handler http.Handler) http.Handler {
159165

160166
// newErrorHandler returns a handler failed requests.
161167
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)
170+
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
171+
})
172+
162173
return func(rw http.ResponseWriter, r *http.Request, err error) {
163174
if err == nil {
164175
klog.Error("error was called with no error")
@@ -171,7 +182,8 @@ func (p *Proxy) newErrorHandler() func(rw http.ResponseWriter, r *http.Request,
171182
// Failed auth
172183
case errUnauthorized:
173184
klog.V(2).Infof("unauthenticated user request %s", r.RemoteAddr)
174-
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
185+
// If Unauthorized then error and report to audit
186+
unauthedHandler.ServeHTTP(rw, r)
175187
return
176188

177189
// User request with impersonation

pkg/proxy/hooks/hooks.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright Jetstack Ltd. See LICENSE for details.
2+
package hooks
3+
4+
import (
5+
"fmt"
6+
"sync"
7+
8+
k8sErrors "k8s.io/apimachinery/pkg/util/errors"
9+
)
10+
11+
type Hooks struct {
12+
preShutdownHooks map[string]ShutdownHook
13+
preShutdownHookLock sync.Mutex
14+
}
15+
16+
type ShutdownHook func() error
17+
18+
func New() *Hooks {
19+
return &Hooks{
20+
preShutdownHooks: make(map[string]ShutdownHook),
21+
}
22+
}
23+
24+
func (h *Hooks) AddPreShutdownHook(name string, hook ShutdownHook) {
25+
h.preShutdownHookLock.Lock()
26+
defer h.preShutdownHookLock.Unlock()
27+
28+
h.preShutdownHooks[name] = hook
29+
}
30+
31+
// RunPreShutdownHooks runs the PreShutdownHooks for the server
32+
func (h *Hooks) RunPreShutdownHooks() error {
33+
var errs []error
34+
35+
h.preShutdownHookLock.Lock()
36+
defer h.preShutdownHookLock.Unlock()
37+
38+
for name, entry := range h.preShutdownHooks {
39+
if err := entry(); err != nil {
40+
errs = append(errs, fmt.Errorf("PreShutdownHook %q failed: %v", name, err))
41+
}
42+
}
43+
44+
return k8sErrors.NewAggregate(errs)
45+
}

pkg/proxy/proxy.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import (
1919
"k8s.io/klog"
2020

2121
"github.com/jetstack/kube-oidc-proxy/cmd/app/options"
22+
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/audit"
2223
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/context"
24+
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/hooks"
2325
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/tokenreview"
2426
)
2527

@@ -43,7 +45,8 @@ type Config struct {
4345
DisableImpersonation bool
4446
TokenReview bool
4547

46-
FlushInterval time.Duration
48+
FlushInterval time.Duration
49+
ExternalAddress string
4750

4851
ExtraUserHeaders map[string][]string
4952
ExtraUserHeadersClientIPEnabled bool
@@ -56,18 +59,23 @@ type Proxy struct {
5659
tokenAuther authenticator.Token
5760
tokenReviewer *tokenreview.TokenReview
5861
secureServingInfo *server.SecureServingInfo
62+
auditor *audit.Audit
5963

6064
restConfig *rest.Config
6165
clientTransport http.RoundTripper
6266
noAuthClientTransport http.RoundTripper
6367

6468
config *Config
6569

70+
hooks *hooks.Hooks
6671
handleError errorHandlerFn
6772
}
6873

69-
func New(restConfig *rest.Config, oidcOptions *options.OIDCAuthenticationOptions,
70-
tokenReviewer *tokenreview.TokenReview, ssinfo *server.SecureServingInfo,
74+
func New(restConfig *rest.Config,
75+
oidcOptions *options.OIDCAuthenticationOptions,
76+
auditOptions *options.AuditOptions,
77+
tokenReviewer *tokenreview.TokenReview,
78+
ssinfo *server.SecureServingInfo,
7179
config *Config) (*Proxy, error) {
7280

7381
// generate tokenAuther from oidc config
@@ -87,13 +95,20 @@ func New(restConfig *rest.Config, oidcOptions *options.OIDCAuthenticationOptions
8795
return nil, err
8896
}
8997

98+
auditor, err := audit.New(auditOptions, config.ExternalAddress, ssinfo)
99+
if err != nil {
100+
return nil, err
101+
}
102+
90103
return &Proxy{
91104
restConfig: restConfig,
105+
hooks: hooks.New(),
92106
tokenReviewer: tokenReviewer,
93107
secureServingInfo: ssinfo,
94108
config: config,
95109
oidcRequestAuther: bearertoken.New(tokenAuther),
96110
tokenAuther: tokenAuther,
111+
auditor: auditor,
97112
}, nil
98113
}
99114

@@ -149,6 +164,11 @@ func (p *Proxy) serve(handler http.Handler, stopCh <-chan struct{}) (<-chan stru
149164
// Setup proxy handlers
150165
handler = p.withHandlers(handler)
151166

167+
// Run auditor
168+
if err := p.auditor.Run(stopCh); err != nil {
169+
return nil, err
170+
}
171+
152172
// securely serve using serving config
153173
waitCh, err := p.secureServingInfo.Serve(handler, time.Second*60, stopCh)
154174
if err != nil {
@@ -240,3 +260,7 @@ func (p *Proxy) roundTripperForRestConfig(config *rest.Config) (http.RoundTrippe
240260
func (p *Proxy) OIDCTokenAuthenticator() authenticator.Token {
241261
return p.tokenAuther
242262
}
263+
264+
func (p *Proxy) RunShutdownHooks() error {
265+
return p.hooks.RunPreShutdownHooks()
266+
}

0 commit comments

Comments
 (0)