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

Commit 9838781

Browse files
authored
Merge pull request #138 from JoshVanL/handler-auditing
Auditing
2 parents 597ffd5 + 8b2ac48 commit 9838781

File tree

28 files changed

+848
-43
lines changed

28 files changed

+848
-43
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
FROM alpine:3.10
33
LABEL description="OIDC reverse proxy authenticator based on Kubernetes"
44

5-
RUN apk --no-cache --update add ca-certificates
5+
RUN apk --no-cache add ca-certificates
66

77
COPY ./bin/kube-oidc-proxy-linux /usr/bin/kube-oidc-proxy
88

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ users:
129129
- [Token Passthrough](./docs/tasks/token-passthrough.md)
130130
- [No Impersonation](./docs/tasks/no-impersonation.md)
131131
- [Extra Impersonations Headers](./docs/tasks/extra-impersonation-headers.md)
132+
- [Auditing](./docs/tasks/auditing.md)
132133

133134
## Development
134135
*NOTE*: building kube-oidc-proxy requires Go version 1.12 or higher.

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.DynamicOptions.Enabled {
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: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,15 @@ func buildRunCommand(stopCh <-chan struct{}, opts *options.Options) *cobra.Comma
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+
p, err := proxy.New(restConfig, opts.OIDCAuthentication, opts.Audit,
8788
tokenReviewer, secureServingInfo, proxyConfig)
8889
if err != nil {
8990
return err
@@ -109,6 +110,10 @@ func buildRunCommand(stopCh <-chan struct{}, opts *options.Options) *cobra.Comma
109110

110111
<-waitCh
111112

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

deploy/charts/kube-oidc-proxy/templates/deployment.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ spec:
7474
{{- if .Values.extraImpersonationHeaders.headers }}
7575
- "--extra-user-headers={{ .Values.extraImpersonationHeaders.headers }}"
7676
{{ end }}
77+
{{- range $key, $value := .Values.extraArgs -}}
78+
- "--{{ $key }}={{ $value -}}"
79+
{{ end }}
7780
resources:
7881
{{- toYaml .Values.resources | nindent 12 }}
7982
env:
@@ -135,14 +138,15 @@ spec:
135138
key: api-audiences
136139
{{ end }}
137140
volumeMounts:
138-
{{ if .Values.oidc.caPEM }}
141+
{{- if .Values.oidc.caPEM }}
139142
- name: kube-oidc-proxy-config
140143
mountPath: /etc/oidc
141144
readOnly: true
142145
{{ end }}
143146
- name: kube-oidc-proxy-tls
144147
mountPath: /etc/oidc/tls
145148
readOnly: true
149+
{{- if .Values.extraVolumeMounts }}{{ toYaml .Values.extraVolumeMounts | trim | nindent 10 }}{{ end }}
146150
volumes:
147151
{{ if .Values.oidc.caPEM }}
148152
- name: kube-oidc-proxy-config
@@ -152,6 +156,7 @@ spec:
152156
- key: oidc.ca-pem
153157
path: oidc-ca.pem
154158
{{ end }}
159+
{{- if .Values.extraVolumes }}{{ toYaml .Values.extraVolumes | trim | nindent 8 }}{{ end }}
155160
- name: kube-oidc-proxy-tls
156161
secret:
157162
secretName: {{ $tlsSecretName }}

deploy/charts/kube-oidc-proxy/values.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ extraImpersonationHeaders:
6060
clientIP: false
6161
#headers: key1=foo,key2=bar,key1=bar
6262

63+
extraArgs: {}
64+
#audit-log-path: /audit-log
65+
#audit-policy-file: /audit/audit.yaml
66+
67+
extraVolumeMounts: {}
68+
#- name: audit
69+
# mountPath: /audit
70+
# readOnly: true
71+
72+
extraVolumes: {}
73+
#- configMap:
74+
#defaultMode: 420
75+
#name: kube-oidc-proxy-policy
76+
#name: audit
6377

6478
ingress:
6579
enabled: false
@@ -91,6 +105,7 @@ resources: {}
91105
# requests:
92106
# cpu: 100m
93107
# memory: 128Mi
108+
#
94109

95110
initContainers: []
96111

docs/tasks/auditing.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Auditing
2+
3+
kube-oidc-proxy allows for the ability to audit requests to the proxy. The proxy
4+
exposes all the same options for auditing that the Kubernetes API server
5+
provides, however does _not_ support dynamic configuration
6+
(`--audit-dynamic-configuration`).
7+
8+
You can read more on how to configure and manage auditing in the [Kubernetes
9+
documentation](https://kubernetes.io/docs/tasks/debug-application-cluster/audit).

pkg/proxy/audit/audit.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
opts *options.AuditOptions
18+
serverConfig *server.CompletedConfig
19+
}
20+
21+
// New creates a new Audit struct to handle auditing for proxy requests. This
22+
// is mostly a wrapper for the apiserver auditing handlers to combine them with
23+
// the proxy.
24+
func New(opts *options.AuditOptions, externalAddress string, secureServingInfo *server.SecureServingInfo) (*Audit, error) {
25+
serverConfig := &server.Config{
26+
ExternalAddress: externalAddress,
27+
SecureServing: secureServingInfo,
28+
29+
// Default to treating watch as a long-running operation.
30+
// Generic API servers have no inherent long-running subresources.
31+
// This is so watch requests are handled correctly in the audit log.
32+
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(
33+
sets.NewString("watch"), sets.NewString()),
34+
}
35+
36+
// We do not support dynamic auditing, so leave nil
37+
if err := opts.ApplyTo(serverConfig, nil, nil, nil, nil); err != nil {
38+
return nil, err
39+
}
40+
41+
completed := serverConfig.Complete(nil)
42+
43+
return &Audit{
44+
opts: opts,
45+
serverConfig: &completed,
46+
}, nil
47+
}
48+
49+
// Run will run the audit backend if configured.
50+
func (a *Audit) Run(stopCh <-chan struct{}) error {
51+
if a.serverConfig.AuditBackend != nil {
52+
if err := a.serverConfig.AuditBackend.Run(stopCh); err != nil {
53+
return fmt.Errorf("failed to run the audit backend: %s", err)
54+
}
55+
}
56+
57+
return nil
58+
}
59+
60+
// Shutdown will shutdown the audit backend if configured.
61+
func (a *Audit) Shutdown() error {
62+
if a.serverConfig.AuditBackend != nil {
63+
a.serverConfig.AuditBackend.Shutdown()
64+
}
65+
66+
return nil
67+
}
68+
69+
// WithRequest will wrap the given handler to inject the request information
70+
// into the context which is then used by the wrapped audit handler.
71+
func (a *Audit) WithRequest(handler http.Handler) http.Handler {
72+
handler = genericapifilters.WithAudit(handler, a.serverConfig.AuditBackend, a.serverConfig.AuditPolicyChecker, a.serverConfig.LongRunningFunc)
73+
return genericapifilters.WithRequestInfo(handler, a.serverConfig.RequestInfoResolver)
74+
}
75+
76+
// WithUnauthorized will wrap the given handler to inject the request
77+
// information into the context which is then used by the wrapped audit
78+
// handler.
79+
func (a *Audit) WithUnauthorized(handler http.Handler) http.Handler {
80+
handler = genericapifilters.WithFailedAuthenticationAudit(handler, a.serverConfig.AuditBackend, a.serverConfig.AuditPolicyChecker)
81+
return genericapifilters.WithRequestInfo(handler, a.serverConfig.RequestInfoResolver)
82+
}

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 & 2 deletions
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")
@@ -170,8 +181,8 @@ func (p *Proxy) newErrorHandler() func(rw http.ResponseWriter, r *http.Request,
170181

171182
// Failed auth
172183
case errUnauthorized:
173-
klog.V(2).Infof("unauthenticated user request %s", r.RemoteAddr)
174-
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
184+
// If Unauthorized then error and report to audit
185+
unauthedHandler.ServeHTTP(rw, r)
175186
return
176187

177188
// User request with impersonation

0 commit comments

Comments
 (0)