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

Commit 0135dd7

Browse files
authored
Merge pull request #84 from JoshVanL/no-impersonation
Adds option to disable impersonation on requests
2 parents fe8dbed + 6521088 commit 0135dd7

File tree

9 files changed

+178
-78
lines changed

9 files changed

+178
-78
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ users:
127127

128128
## Configuration
129129
- [Token Passthrough](./docs/tasks/token-passthrough.md)
130+
- [No Impersonation](./docs/tasks/no-impersonation.md)
130131

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

cmd/options/kube_oidc_proxy.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright Jetstack Ltd. See LICENSE for details.
2+
package options
3+
4+
import (
5+
"github.com/spf13/pflag"
6+
)
7+
8+
type TokenPassthroughOptions struct {
9+
Audiences []string
10+
Enabled bool
11+
}
12+
13+
type KubeOIDCProxyOptions struct {
14+
DisableImpersonation bool
15+
TokenPassthrough TokenPassthroughOptions
16+
}
17+
18+
func (t *TokenPassthroughOptions) AddFlags(fs *pflag.FlagSet) {
19+
fs.StringSliceVar(&t.Audiences, "token-passthrough-audiences", t.Audiences, ""+
20+
"(Alpha) List of the identifiers that the resource server presented with the token "+
21+
"identifies as. The resoure server will verify that non OIDC tokens are intended "+
22+
"for at least one of the audiences in this list. If no audiences are "+
23+
"provided, the audience will default to the audience of the Kubernetes "+
24+
"apiserver.")
25+
26+
fs.BoolVar(&t.Enabled, "token-passthrough", t.Enabled, ""+
27+
"(Alpha) Requests with Bearer tokens that fail OIDC validation are tried against "+
28+
"the API server using the Token Review endpoint. If successful, the request "+
29+
"is sent on as is, with no impersonation.")
30+
}
31+
32+
func (k *KubeOIDCProxyOptions) AddFlags(fs *pflag.FlagSet) {
33+
fs.BoolVar(&k.DisableImpersonation, "disable-impersonation", k.DisableImpersonation,
34+
"(Alpha) Disable the impersonation of authenticated requests. All "+
35+
"authenticated requests will be forwarded as is.")
36+
37+
k.TokenPassthrough.AddFlags(fs)
38+
}

cmd/options/options.go renamed to cmd/options/oidc.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,6 @@ import (
99
cliflag "k8s.io/component-base/cli/flag"
1010
)
1111

12-
type TokenPassthroughOptions struct {
13-
Audiences []string
14-
Enabled bool
15-
}
16-
17-
func (t *TokenPassthroughOptions) AddFlags(fs *pflag.FlagSet) {
18-
fs.StringSliceVar(&t.Audiences, "token-passthrough-audiences", t.Audiences, ""+
19-
"List of the identifiers that the resource server presented with the token "+
20-
"identifies as. The resoure server will verify that non OIDC tokens are intended "+
21-
"for at least one of the audiences in this list. If no audiences are "+
22-
"provided, the audience will default to the audience of the Kubernetes "+
23-
"apiserver.")
24-
25-
fs.BoolVar(&t.Enabled, "token-passthrough", t.Enabled, ""+
26-
"Requests with Bearer tokens that fail OIDC validation are tried against "+
27-
"the API server using the Token Review endpoint. If successful, the request "+
28-
"is sent on as is, with no impersonation.")
29-
}
30-
3112
type OIDCAuthenticationOptions struct {
3213
APIAudiences []string
3314
CAFile string

cmd/run.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
4545
}
4646
ssoptionsWithLB := ssoptions.WithLoopback()
4747

48-
tpOptions := new(options.TokenPassthroughOptions)
48+
kopOptions := new(options.KubeOIDCProxyOptions)
4949

5050
clientConfigFlags := genericclioptions.NewConfigFlags(true)
5151

@@ -98,8 +98,8 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
9898

9999
// Init token reviewer if enabled
100100
var tokenReviewer *tokenreview.TokenReview
101-
if tpOptions.Enabled {
102-
tokenReviewer, err = tokenreview.New(restConfig, tpOptions.Audiences)
101+
if kopOptions.TokenPassthrough.Enabled {
102+
tokenReviewer, err = tokenreview.New(restConfig, kopOptions.TokenPassthrough.Audiences)
103103
if err != nil {
104104
return err
105105
}
@@ -112,8 +112,13 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
112112
return err
113113
}
114114

115-
p := proxy.New(restConfig, reqAuther, tokenReviewer,
116-
secureServingInfo)
115+
proxyOptions := &proxy.Options{
116+
TokenReview: kopOptions.TokenPassthrough.Enabled,
117+
DisableImpersonation: kopOptions.DisableImpersonation,
118+
}
119+
120+
p := proxy.New(restConfig, reqAuther,
121+
tokenReviewer, secureServingInfo, proxyOptions)
117122

118123
// run proxy
119124
waitCh, err := p.Run(stopCh)
@@ -134,12 +139,12 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
134139
var namedFlagSets cliflag.NamedFlagSets
135140
fs := cmd.Flags()
136141

142+
kopfs := namedFlagSets.FlagSet("Kube-OIDC-Proxy")
143+
kopOptions.AddFlags(kopfs)
144+
137145
oidcfs := namedFlagSets.FlagSet("OIDC")
138146
oidcOptions.AddFlags(oidcfs)
139147

140-
tpfs := namedFlagSets.FlagSet("Token Passthrough (Alpha)")
141-
tpOptions.AddFlags(tpfs)
142-
143148
ssoptionsWithLB.AddFlags(namedFlagSets.FlagSet("secure serving"))
144149

145150
clientConfigFlags.CacheDir = nil

docs/tasks/no-impersonation.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# No Impersonation
2+
3+
kube-oidc-proxy can be configured to disable impersonation. When a request has
4+
been successfully authenticated, the request is forwarded as-is, without changes
5+
to the HTTP header and no authentication injected by the proxy. The OIDC
6+
bearer token is also kept in the request. This can be useful for securing
7+
endpoints that do not provide OIDC or any authentication methods and do not
8+
implement any authorization.
9+
10+
To disable impersonation, provide the following flag:
11+
12+
```
13+
--disable-impersonation
14+
```

pkg/e2e/e2e.go

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -175,31 +175,10 @@ func (e *E2E) newIssuerProxyPair() (*http.Transport, error) {
175175
}
176176
e.proxyPort = proxyPort
177177

178-
cmd := exec.Command("../../kube-oidc-proxy",
179-
fmt.Sprintf("--oidc-issuer-url=https://127.0.0.1:%s", issuer.Port()),
180-
fmt.Sprintf("--oidc-ca-file=%s", e.issuer.KeyCertPair().CertPath),
181-
"--oidc-client-id=kube-oidc-proxy_e2e_client-id",
182-
"--oidc-username-claim=e2e-username-claim",
183-
"--oidc-groups-claim=e2e-groups-claim",
184-
"--oidc-signing-algs=RS256",
185-
186-
"--bind-address=127.0.0.1",
187-
fmt.Sprintf("--secure-port=%s", e.proxyPort),
188-
fmt.Sprintf("--tls-cert-file=%s", e.proxyKeyCertPair.CertPath),
189-
fmt.Sprintf("--tls-private-key-file=%s", e.proxyKeyCertPair.KeyPath),
190-
191-
fmt.Sprintf("--kubeconfig=%s", e.kubeKubeconfig),
192-
)
193-
cmd.Stderr = os.Stderr
194-
cmd.Stdout = os.Stdout
195-
if err := cmd.Start(); err != nil {
178+
if err := e.runProxy(); err != nil {
196179
return nil, err
197180
}
198181

199-
e.proxyCmd = cmd
200-
201-
time.Sleep(time.Second * 13)
202-
203182
return transport, nil
204183
}
205184

@@ -266,9 +245,9 @@ func (e *E2E) cleanup() {
266245
klog.Errorf("failed to kill kube-oidc-proxy process: %s",
267246
err)
268247
}
269-
270-
e.proxyCmd = nil
271248
}
249+
250+
e.proxyCmd = nil
272251
}
273252

274253
func (e *E2E) runProxy(extraArgs ...string) error {

pkg/e2e/noimpersonation_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright Jetstack Ltd. See LICENSE for details.
2+
package e2e
3+
4+
import (
5+
"fmt"
6+
"testing"
7+
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
const (
13+
namespaceNoImpersonation = "kube-oidc-proxy-e2e-no-impersonation"
14+
)
15+
16+
func TestNoImpersonation(t *testing.T) {
17+
if e2eSuite == nil {
18+
t.Skip("e2eSuite not defined")
19+
return
20+
}
21+
22+
coreclient := e2eSuite.kubeclient.CoreV1()
23+
24+
_, err := coreclient.Namespaces().Create(&corev1.Namespace{
25+
ObjectMeta: metav1.ObjectMeta{
26+
Name: namespaceNoImpersonation,
27+
},
28+
})
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
33+
e2eSuite.cleanup()
34+
defer e2eSuite.cleanup()
35+
36+
if err := e2eSuite.runProxy("--disable-impersonation"); err != nil {
37+
t.Error(err)
38+
t.FailNow()
39+
}
40+
41+
url := fmt.Sprintf(
42+
"https://127.0.0.1:%s/api/v1/namespaces/%s/pods",
43+
e2eSuite.proxyPort,
44+
namespaceNoImpersonation,
45+
)
46+
47+
// Should return an unathorized response from the API server - we authed with
48+
// the proxy but the API server isn't set up for our OIDC auth
49+
e2eSuite.testToken(t, e2eSuite.validToken(), url, 401,
50+
`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}`)
51+
}

pkg/proxy/proxy.go

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
utilnet "k8s.io/apimachinery/pkg/util/net"
1314
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
1415
authuser "k8s.io/apiserver/pkg/authentication/user"
1516
"k8s.io/apiserver/pkg/server"
@@ -31,35 +32,46 @@ var (
3132
impersonateExtraHeader = strings.ToLower(transport.ImpersonateUserExtraHeaderPrefix)
3233
)
3334

35+
type Options struct {
36+
DisableImpersonation bool
37+
TokenReview bool
38+
}
39+
3440
type Proxy struct {
3541
oidcAuther *bearertoken.Authenticator
36-
tokenAuther *tokenreview.TokenReview
42+
tokenReviewer *tokenreview.TokenReview
3743
secureServingInfo *server.SecureServingInfo
3844

3945
restConfig *rest.Config
4046
clientTransport http.RoundTripper
4147
noAuthClientTransport http.RoundTripper
48+
49+
options *Options
4250
}
4351

4452
func New(restConfig *rest.Config, oidcAuther *bearertoken.Authenticator,
45-
tokenAuther *tokenreview.TokenReview, ssinfo *server.SecureServingInfo) *Proxy {
53+
tokenReviewer *tokenreview.TokenReview, ssinfo *server.SecureServingInfo, options *Options) *Proxy {
4654
return &Proxy{
4755
restConfig: restConfig,
4856
oidcAuther: oidcAuther,
49-
tokenAuther: tokenAuther,
57+
tokenReviewer: tokenReviewer,
5058
secureServingInfo: ssinfo,
59+
options: options,
5160
}
5261
}
5362

5463
func (p *Proxy) Run(stopCh <-chan struct{}) (<-chan struct{}, error) {
64+
klog.Infof("waiting for oidc provider to become ready...")
65+
66+
// standard round tripper for proxy to API Server
5567
clientRT, err := p.roundTripperForRestConfig(p.restConfig)
5668
if err != nil {
5769
return nil, err
5870
}
5971
p.clientTransport = clientRT
6072

6173
// No auth round tripper for no impersonation
62-
if p.tokenAuther != nil {
74+
if p.options.DisableImpersonation || p.options.TokenReview {
6375
noAuthClientRT, err := p.roundTripperForRestConfig(&rest.Config{
6476
APIPath: p.restConfig.APIPath,
6577
Host: p.restConfig.Host,
@@ -112,42 +124,38 @@ func (p *Proxy) serve(proxyHandler *httputil.ReverseProxy, stopCh <-chan struct{
112124
}
113125

114126
func (p *Proxy) RoundTrip(req *http.Request) (*http.Response, error) {
115-
// auth request and handle unauthed
116-
info, ok, err := p.oidcAuther.AuthenticateRequest(req)
127+
// Clone the request here since successfully authenticating the request
128+
// deletes those auth headers
129+
reqCpy := utilnet.CloneRequest(req)
117130

131+
// auth request and handle unauthed
132+
info, ok, err := p.oidcAuther.AuthenticateRequest(reqCpy)
118133
if err != nil {
119134

120135
// attempt to passthrough request if valid token
121-
if p.tokenAuther != nil {
122-
klog.V(4).Infof("attempting to validate a token in request using TokenReview endpoint(%s)",
123-
req.RemoteAddr)
124-
125-
ok, tkErr := p.tokenAuther.Review(req)
126-
// no error so passthrough the request
127-
if tkErr == nil && ok {
128-
klog.V(4).Infof("passing request with valid token through (%s)",
129-
req.RemoteAddr)
130-
// Don't set impersonation headers and pass through without proxy auth
131-
// and headers still set
132-
return p.noAuthClientTransport.RoundTrip(req)
133-
}
134-
135-
if tkErr != nil {
136-
err = fmt.Errorf("%s, %s", err, tkErr)
137-
}
136+
if p.options.TokenReview {
137+
return p.tokenReview(reqCpy)
138138
}
139139

140-
klog.Errorf("unable to authenticate the request due to an error (%s): %s",
141-
req.RemoteAddr, err)
142140
return nil, errUnauthorized
143141
}
144142

143+
// failed authorization
145144
if !ok {
146145
return nil, errUnauthorized
147146
}
148147

148+
klog.V(4).Infof("authenticated request: %s", reqCpy.RemoteAddr)
149+
150+
// if we have disabled impersonation we can forward the request right away
151+
if p.options.DisableImpersonation {
152+
klog.V(2).Infof("passing on request with no impersonation: %s", reqCpy.RemoteAddr)
153+
// Send original copy here with auth header intact
154+
return p.noAuthClientTransport.RoundTrip(req)
155+
}
156+
149157
// check for incoming impersonation headers and reject if any exists
150-
if p.hasImpersonation(req.Header) {
158+
if p.hasImpersonation(reqCpy.Header) {
151159
return nil, errImpersonateHeader
152160
}
153161

@@ -182,7 +190,29 @@ func (p *Proxy) RoundTrip(req *http.Request) (*http.Response, error) {
182190
rt := transport.NewImpersonatingRoundTripper(conf, p.clientTransport)
183191

184192
// push request through round trippers to the API server
185-
return rt.RoundTrip(req)
193+
return rt.RoundTrip(reqCpy)
194+
}
195+
196+
func (p *Proxy) tokenReview(req *http.Request) (*http.Response, error) {
197+
klog.V(4).Infof("attempting to validate a token in request using TokenReview endpoint(%s)",
198+
req.RemoteAddr)
199+
200+
ok, err := p.tokenReviewer.Review(req)
201+
// no error so passthrough the request
202+
if err == nil && ok {
203+
klog.V(4).Infof("passing request with valid token through (%s)",
204+
req.RemoteAddr)
205+
// Don't set impersonation headers and pass through without proxy auth
206+
// and headers still set
207+
return p.noAuthClientTransport.RoundTrip(req)
208+
}
209+
210+
if err != nil {
211+
klog.Errorf("unable to authenticate the request via TokenReview due to an error (%s): %s",
212+
req.RemoteAddr, err)
213+
}
214+
215+
return nil, errUnauthorized
186216
}
187217

188218
func (p *Proxy) hasImpersonation(header http.Header) bool {

pkg/proxy/proxy_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ func newTestProxy(t *testing.T) *fakeProxy {
259259
Proxy: &Proxy{
260260
oidcAuther: bearertoken.New(fakeToken),
261261
clientTransport: fakeRT,
262+
options: new(Options),
262263
},
263264
}
264265
}

0 commit comments

Comments
 (0)