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

Commit b5d7a59

Browse files
authored
Merge pull request #128 from JoshVanL/user-extra-headers
Extra User Info Impersonation Headers
2 parents c0c5ceb + 5762ae9 commit b5d7a59

File tree

34 files changed

+876
-72
lines changed

34 files changed

+876
-72
lines changed

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@ go_lint: $(BINDIR)/golangci-lint ## lint golang code for problems
7676
clean: ## clean up created files
7777
rm -rf \
7878
$(BINDIR) \
79-
pkg/mocks/authenticator.go \
80-
demo/bin \
81-
test/e2e/framework/issuer/bin
79+
$(CURDIR)/pkg/mocks/authenticator.go \
80+
$(CURDIR)/demo/bin \
81+
$(CURDIR)/test/e2e/framework/issuer/bin \
82+
$(CURDIR)/test/e2e/framework/fake-apiserver/bin
8283

8384
verify: depend verify_boilerplate go_fmt go_vet go_lint ## verify code and mod
8485

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ users:
128128
## Configuration
129129
- [Token Passthrough](./docs/tasks/token-passthrough.md)
130130
- [No Impersonation](./docs/tasks/no-impersonation.md)
131+
- [Extra Impersonations Headers](./docs/tasks/extra-impersonation-headers.md)
131132

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

cmd/app/options/kube_oidc_proxy.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,39 @@ package options
33

44
import (
55
"github.com/spf13/pflag"
6+
7+
"github.com/jetstack/kube-oidc-proxy/pkg/util/flags"
68
)
79

10+
type KubeOIDCProxyOptions struct {
11+
DisableImpersonation bool
12+
ReadinessProbePort int
13+
14+
TokenPassthrough TokenPassthroughOptions
15+
ExtraHeaderOptions ExtraHeaderOptions
16+
}
17+
818
type TokenPassthroughOptions struct {
919
Audiences []string
1020
Enabled bool
1121
}
1222

13-
type KubeOIDCProxyOptions struct {
14-
DisableImpersonation bool
15-
TokenPassthrough TokenPassthroughOptions
23+
type ExtraHeaderOptions struct {
24+
EnableClientIPExtraUserHeader bool
1625

17-
ReadinessProbePort int
26+
ExtraUserHeaders map[string][]string
27+
}
28+
29+
func (k *KubeOIDCProxyOptions) AddFlags(fs *pflag.FlagSet) {
30+
fs.BoolVar(&k.DisableImpersonation, "disable-impersonation", k.DisableImpersonation,
31+
"(Alpha) Disable the impersonation of authenticated requests. All "+
32+
"authenticated requests will be forwarded as is.")
33+
34+
fs.IntVarP(&k.ReadinessProbePort, "readiness-probe-port", "P", 8080,
35+
"Port to expose readiness probe.")
36+
37+
k.TokenPassthrough.AddFlags(fs)
38+
k.ExtraHeaderOptions.AddFlags(fs)
1839
}
1940

2041
func (t *TokenPassthroughOptions) AddFlags(fs *pflag.FlagSet) {
@@ -31,13 +52,15 @@ func (t *TokenPassthroughOptions) AddFlags(fs *pflag.FlagSet) {
3152
"is sent on as is, with no impersonation.")
3253
}
3354

34-
func (k *KubeOIDCProxyOptions) AddFlags(fs *pflag.FlagSet) {
35-
fs.BoolVar(&k.DisableImpersonation, "disable-impersonation", k.DisableImpersonation,
36-
"(Alpha) Disable the impersonation of authenticated requests. All "+
37-
"authenticated requests will be forwarded as is.")
38-
39-
fs.IntVarP(&k.ReadinessProbePort, "readiness-probe-port", "P", 8080,
40-
"Port to expose readiness probe.")
55+
func (e *ExtraHeaderOptions) AddFlags(fs *pflag.FlagSet) {
56+
fs.BoolVar(&e.EnableClientIPExtraUserHeader, "extra-user-header-client-ip",
57+
e.EnableClientIPExtraUserHeader, "(Alpha) If enabled, proxied requests will "+
58+
"include the extra user header 'Impersonate-Extra-Remote-Client-IP: "+
59+
"<REMOTE_ADDR>' where <REMOTE_ADDR> will contain the remote address of "+
60+
"the source of the request.")
4161

42-
k.TokenPassthrough.AddFlags(fs)
62+
fs.Var(flags.NewStringToStringSliceValue(&e.ExtraUserHeaders), "extra-user-headers",
63+
"(Alpha) A list of key value pairs of extra user headers to pass with "+
64+
"proxied requests as part of the impersonated request. A single key can "+
65+
"hold multiple values.")
4366
}

cmd/app/run.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
5151
Use: appName,
5252
Long: "kube-oidc-proxy is a reverse proxy to authenticate users to Kubernetes API servers with Open ID Connect Authentication.",
5353
RunE: func(cmd *cobra.Command, args []string) error {
54-
var err error
55-
5654
if cmd.Flag("version").Value.String() == "true" {
5755
version.PrintVersionAndExit()
5856
}
@@ -65,6 +63,7 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
6563
return errors.New("unable to securely serve on port 8080, used by readiness prob")
6664
}
6765

66+
var err error
6867
var restConfig *rest.Config
6968
if clientConfigOptions.ClientFlagsChanged(cmd) {
7069
// one or more client flags have been set to use client flag built
@@ -100,6 +99,9 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
10099
proxyOptions := &proxy.Options{
101100
TokenReview: kopOptions.TokenPassthrough.Enabled,
102101
DisableImpersonation: kopOptions.DisableImpersonation,
102+
103+
ExtraUserHeaders: kopOptions.ExtraHeaderOptions.ExtraUserHeaders,
104+
ExtraUserHeadersClientIPEnabled: kopOptions.ExtraHeaderOptions.EnableClientIPExtraUserHeader,
103105
}
104106

105107
// Initialise proxy with OIDC token authenticator

deploy/charts/kube-oidc-proxy/templates/_helpers.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ Required claims serialized to CLI argument
5555
{{- end -}}
5656
{{ join "," $local }}
5757
{{- end -}}
58-
{{- end -}}
58+
{{- end -}}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ spec:
6868
- "--token-passthrough-audiences={{ join "," .Values.tokenPassthrough.audiences }}"
6969
{{ end }}
7070
{{ end }}
71+
{{- if .Values.extraImpersonationHeaders.clientIP }}
72+
- "--extra-user-header-client-ip"
73+
{{ end }}
74+
{{- if .Values.extraImpersonationHeaders.headers }}
75+
- "--extra-user-headers={{ .Values.extraImpersonationHeaders.headers }}"
76+
{{ end }}
7177
resources:
7278
{{- toYaml .Values.resources | nindent 12 }}
7379
env:

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ tokenPassthrough:
5454
enabled: false
5555
audiences: []
5656

57+
# To add extra impersonation headers
58+
# https://github.com/jetstack/kube-oidc-proxy/blob/master/docs/tasks/extra-impersonation-headers.md
59+
extraImpersonationHeaders:
60+
clientIP: false
61+
#headers: key1=foo,key2=bar,key1=bar
62+
63+
5764
ingress:
5865
enabled: false
5966
annotations: {}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Extra Impersonation Headers
2+
3+
kube-oidc-proxy has support for adding 'extra' headers to the impersonation user
4+
info. This can be useful for passing extra information onto the target server
5+
about the proxy or client. kube-oidc-proxy currently supports two configuration
6+
options.
7+
8+
# Client IP
9+
10+
The following flag can be passed which will append the remote client IP as an
11+
extra header:
12+
13+
`--extra-user-header-client-ip`
14+
15+
Proxied requests will then contain the header
16+
`Impersonate-Extra-Remote-Client-Ip: <REMOTE_ADDR>` where `<REMOTE_ADDR>` is
17+
the address of the source connection of the request. Note that this IP address
18+
may not be the real client IP address when the request is being proxied.
19+
20+
# Extra User Headers
21+
22+
The following flag accepts a number of key value pairs that will be added as
23+
extra impersonation headers with proxied requests. This flag accepts a number of
24+
key value pairs, separated by commas, where a single key may have multiple
25+
values:
26+
27+
`--extra-user-headers=key1=foo,key2=bar,key1=bar`
28+
29+
Proxied requests will then contain the headers
30+
31+
`Impersonate-Extra-Key1: foo,bar`
32+
`Impersonate-Extra-Key2: foo`

pkg/proxy/proxy.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import (
2424
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/tokenreview"
2525
)
2626

27+
const (
28+
UserHeaderClientIPKey = "Remote-Client-IP"
29+
)
30+
2731
var (
2832
errUnauthorized = errors.New("Unauthorized")
2933
errImpersonateHeader = errors.New("Impersonate-User in header")
@@ -38,6 +42,9 @@ var (
3842
type Options struct {
3943
DisableImpersonation bool
4044
TokenReview bool
45+
46+
ExtraUserHeaders map[string][]string
47+
ExtraUserHeadersClientIPEnabled bool
4148
}
4249

4350
type Proxy struct {
@@ -195,12 +202,36 @@ func (p *Proxy) RoundTrip(req *http.Request) (*http.Response, error) {
195202
groups = append(groups, authuser.AllAuthenticated)
196203
}
197204

198-
// set impersonation header using authenticated user identity
205+
extra := user.GetExtra()
206+
207+
if extra == nil {
208+
extra = make(map[string][]string)
209+
}
210+
211+
// If client IP user extra header option set then append the remote client
212+
// address.
213+
if p.options.ExtraUserHeadersClientIPEnabled {
214+
klog.V(6).Infof("adding impersonate extra user header %s: %s (%s)",
215+
UserHeaderClientIPKey, req.RemoteAddr, reqCpy.RemoteAddr)
216+
217+
extra[UserHeaderClientIPKey] = append(extra[UserHeaderClientIPKey], req.RemoteAddr)
218+
}
219+
220+
// Add custom extra user headers to impersonation request
221+
for k, vs := range p.options.ExtraUserHeaders {
222+
for _, v := range vs {
223+
klog.V(6).Infof("adding impersonate extra user header %s: %s (%s)",
224+
k, v, reqCpy.RemoteAddr)
225+
226+
extra[k] = append(extra[k], v)
227+
}
228+
}
199229

230+
// Set impersonation header using authenticated user identity.
200231
conf := transport.ImpersonationConfig{
201232
UserName: user.GetName(),
202233
Groups: groups,
203-
Extra: user.GetExtra(),
234+
Extra: extra,
204235
}
205236

206237
rt := transport.NewImpersonatingRoundTripper(conf, p.clientTransport)

pkg/proxy/proxy_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,3 +400,88 @@ func Test_RoundTrip(t *testing.T) {
400400
t.Errorf("unexpected round trip error, exp=nil got=%s", err)
401401
}
402402
}
403+
404+
func TestExtraHeadersOptions(t *testing.T) {
405+
remoteAddr := "8.8.8.8"
406+
407+
tests := map[string]struct {
408+
options *Options
409+
expExtra map[string][]string
410+
}{
411+
"if no extra headers set or client IP enabled then expect no extras": {
412+
options: &Options{
413+
ExtraUserHeaders: nil,
414+
ExtraUserHeadersClientIPEnabled: false,
415+
},
416+
expExtra: nil,
417+
},
418+
"if extra headers set but no client IP enabled then should return added extras": {
419+
options: &Options{
420+
ExtraUserHeaders: map[string][]string{
421+
"foo": []string{"a", "b"},
422+
"bar": []string{"c", "d", "e"},
423+
},
424+
ExtraUserHeadersClientIPEnabled: false,
425+
},
426+
expExtra: map[string][]string{
427+
"Impersonate-Extra-Foo": []string{"a", "b"},
428+
"Impersonate-Extra-Bar": []string{"c", "d", "e"},
429+
},
430+
},
431+
"if no extra headers set but client IP enabled then should return added client IP": {
432+
options: &Options{
433+
ExtraUserHeaders: nil,
434+
ExtraUserHeadersClientIPEnabled: true,
435+
},
436+
expExtra: map[string][]string{
437+
"Impersonate-Extra-Remote-Client-Ip": []string{"8.8.8.8"},
438+
},
439+
},
440+
"if extra headers set and client IP enabled then should return extra headers and client IP": {
441+
options: &Options{
442+
ExtraUserHeaders: map[string][]string{
443+
"foo": []string{"a", "b"},
444+
"bar": []string{"c", "d", "e"},
445+
},
446+
ExtraUserHeadersClientIPEnabled: true,
447+
},
448+
expExtra: map[string][]string{
449+
"Impersonate-Extra-Foo": []string{"a", "b"},
450+
"Impersonate-Extra-Bar": []string{"c", "d", "e"},
451+
"Impersonate-Extra-Remote-Client-Ip": []string{"8.8.8.8"},
452+
},
453+
},
454+
}
455+
456+
for name, test := range tests {
457+
t.Run(name, func(t *testing.T) {
458+
p := newTestProxy(t)
459+
p.options = test.options
460+
461+
req := &http.Request{
462+
Header: http.Header{
463+
"Authorization": []string{"bearer fake-token"},
464+
},
465+
RemoteAddr: remoteAddr,
466+
}
467+
468+
authResponse := &authenticator.Response{
469+
User: &user.DefaultInfo{
470+
Name: "a-user",
471+
Groups: []string{authuser.AllAuthenticated},
472+
},
473+
}
474+
475+
p.fakeToken.EXPECT().AuthenticateToken(gomock.Any(), "fake-token").Return(authResponse, true, nil)
476+
477+
p.fakeRT.expUser = "a-user"
478+
p.fakeRT.expGroup = []string{authuser.AllAuthenticated}
479+
p.fakeRT.expExtra = test.expExtra
480+
481+
_, err := p.RoundTrip(req)
482+
if err != nil {
483+
t.Errorf("got unexpected error: %s", err)
484+
}
485+
})
486+
}
487+
}

0 commit comments

Comments
 (0)