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

Commit 182fa79

Browse files
authored
Merge pull request #83 from JoshVanL/sa-passthrough-v2
Token Passthrough
2 parents c5ca429 + 0cf02d1 commit 182fa79

19 files changed

+873
-174
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,8 @@ users:
125125
name: oidc
126126
```
127127

128+
## Configuration
129+
- [Token Passthrough](./docs/tasks/token-passthrough.md)
130+
128131
## Development
129132
*NOTE*: building kube-oidc-proxy requires Go version 1.12 or higher.

cmd/options/options.go

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@ 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+
1231
type OIDCAuthenticationOptions struct {
1332
APIAudiences []string
1433
CAFile string
@@ -32,26 +51,23 @@ func (o *OIDCAuthenticationOptions) Validate() error {
3251

3352
func (o *OIDCAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
3453
fs.StringSliceVar(&o.APIAudiences, "api-audiences", o.APIAudiences, ""+
35-
"Identifiers of the API. The service account token authenticator will validate that "+
36-
"tokens used against the API are bound to at least one of these audiences. If the "+
37-
"--service-account-issuer flag is configured and this flag is not, this field "+
38-
"defaults to a single element list containing the issuer URL .")
54+
"Identifiers of the API. This can be used as an additional list of "+
55+
"identifiers that exist in the target audiences of requests when "+
56+
"authenticating with OIDC.")
3957

4058
fs.StringVar(&o.IssuerURL, "oidc-issuer-url", o.IssuerURL, ""+
41-
"The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+
42-
"If set, it will be used to verify the OIDC JSON Web Token (JWT).")
59+
"The URL of the OpenID issuer, only HTTPS scheme will be accepted.")
4360

4461
fs.StringVar(&o.ClientID, "oidc-client-id", o.ClientID,
45-
"The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.")
62+
"The client ID for the OpenID Connect client.")
4663

4764
fs.StringVar(&o.CAFile, "oidc-ca-file", o.CAFile, ""+
48-
"If set, the OpenID server's certificate will be verified by one of the authorities "+
49-
"in the oidc-ca-file, otherwise the host's root CA set will be used.")
65+
"The OpenID server's certificate will be verified by one of the authorities "+
66+
"in the oidc-ca-file, otherwise the host's root CA set will be used")
5067

5168
fs.StringVar(&o.UsernameClaim, "oidc-username-claim", "sub", ""+
52-
"The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+
53-
"is not guaranteed to be unique and immutable. This flag is experimental, please see "+
54-
"the authentication documentation for further details.")
69+
"The OpenID claim to use as the username. Note that claims other than the default ('sub') "+
70+
"is not guaranteed to be unique and immutable")
5571

5672
fs.StringVar(&o.UsernamePrefix, "oidc-username-prefix", "", ""+
5773
"If provided, all usernames will be prefixed with this value. If not provided, "+
@@ -60,8 +76,7 @@ func (o *OIDCAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
6076

6177
fs.StringVar(&o.GroupsClaim, "oidc-groups-claim", "", ""+
6278
"If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
63-
"The claim value is expected to be a string or array of strings. This flag is experimental, "+
64-
"please see the authentication documentation for further details.")
79+
"The claim value is expected to be a string or array of strings.")
6580

6681
fs.StringVar(&o.GroupsPrefix, "oidc-groups-prefix", "", ""+
6782
"If provided, all groups will be prefixed with this value to prevent conflicts with "+

cmd/run.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/jetstack/kube-oidc-proxy/cmd/options"
2323
"github.com/jetstack/kube-oidc-proxy/pkg/probe"
2424
"github.com/jetstack/kube-oidc-proxy/pkg/proxy"
25+
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/tokenreview"
2526
"github.com/jetstack/kube-oidc-proxy/pkg/version"
2627
)
2728

@@ -44,6 +45,8 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
4445
}
4546
ssoptionsWithLB := ssoptions.WithLoopback()
4647

48+
tpOptions := new(options.TokenPassthroughOptions)
49+
4750
clientConfigFlags := genericclioptions.NewConfigFlags(true)
4851

4952
healthCheck := probe.New(strconv.Itoa(readinessProbePort))
@@ -93,14 +96,24 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
9396
return err
9497
}
9598

99+
// Init token reviewer if enabled
100+
var tokenReviewer *tokenreview.TokenReview
101+
if tpOptions.Enabled {
102+
tokenReviewer, err = tokenreview.New(restConfig, tpOptions.Audiences)
103+
if err != nil {
104+
return err
105+
}
106+
}
107+
96108
// oidc auther from config
97109
reqAuther := bearertoken.New(oidcAuther)
98110
secureServingInfo := new(server.SecureServingInfo)
99111
if err := ssoptionsWithLB.ApplyTo(&secureServingInfo, nil); err != nil {
100112
return err
101113
}
102114

103-
p := proxy.New(restConfig, reqAuther, secureServingInfo)
115+
p := proxy.New(restConfig, reqAuther, tokenReviewer,
116+
secureServingInfo)
104117

105118
// run proxy
106119
waitCh, err := p.Run(stopCh)
@@ -124,6 +137,9 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
124137
oidcfs := namedFlagSets.FlagSet("OIDC")
125138
oidcOptions.AddFlags(oidcfs)
126139

140+
tpfs := namedFlagSets.FlagSet("Token Passthrough (Alpha)")
141+
tpOptions.AddFlags(tpfs)
142+
127143
ssoptionsWithLB.AddFlags(namedFlagSets.FlagSet("secure serving"))
128144

129145
clientConfigFlags.CacheDir = nil

docs/tasks/token-passthrough.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Token Passthrough
2+
3+
kube-oidc-proxy can be configured to enable 'token passthrough' for tokens that
4+
fail OIDC authentication. If enabled, kube-oidc-proxy will perform a [token
5+
review](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication)
6+
API call to the configured target backend using the Kubernetes API. If
7+
successful, the request will be passed through as-is, with the token intact in
8+
the request and no other authentication used by kube-oidc-proxy.
9+
10+
To enable token passthrough, include the following flag:
11+
12+
```
13+
--token-passthrough
14+
```
15+
16+
In the case of the Kubernetes API server, the authenticator, if audience aware,
17+
will validate the audiences of tokens using the audience of the API server. A
18+
new set of audiences can also be given which will be used to validate the token
19+
against. At least one of these audiences need to be present in the audiences of
20+
the token to be successful:
21+
22+
```
23+
---token-passthrough-audiences=aud1.foo.bar,aud2.foo.bar
24+
```

pkg/e2e/e2e.go

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"bytes"
66
"crypto/tls"
77
"crypto/x509"
8+
"errors"
89
"fmt"
910
"io/ioutil"
1011
"net/http"
@@ -40,9 +41,10 @@ type E2E struct {
4041
proxyClient *http.Client
4142
proxyCmd *exec.Cmd
4243
proxyPort string
43-
proxyCert []byte
4444
proxyTransport *http.Transport
4545

46+
proxyKeyCertPair *util.KeyCertPair
47+
4648
tmpDir string
4749
}
4850

@@ -141,24 +143,24 @@ func (e *E2E) newIssuerProxyPair() (*http.Transport, error) {
141143
}
142144
e.issuer = issuer
143145

144-
proxyCertPath, proxyKeyPath, _, proxyCert, err := util.NewTLSSelfSignedCertKey(pairTmpDir, "")
146+
pkcp, err := util.NewTLSSelfSignedCertKey(pairTmpDir, "")
145147
if err != nil {
146148
return nil, fmt.Errorf("failed to create key pair: %s", err)
147149
}
148-
e.proxyCert = proxyCert
150+
e.proxyKeyCertPair = pkcp
149151

150152
signer, err := jose.NewSigner(jose.SigningKey{
151153
Algorithm: jose.SignatureAlgorithm("RS256"),
152-
Key: issuer.Key(),
154+
Key: issuer.KeyCertPair().Key,
153155
}, nil)
154156
if err != nil {
155157
return nil, fmt.Errorf("failed to initialise new jwt signer: %s", err)
156158
}
157159
e.signer = signer
158160

159161
certPool := x509.NewCertPool()
160-
if ok := certPool.AppendCertsFromPEM(proxyCert); !ok {
161-
return nil, fmt.Errorf("failed to append proxy cert data to cert pool %s", proxyCertPath)
162+
if ok := certPool.AppendCertsFromPEM(pkcp.Cert); !ok {
163+
return nil, fmt.Errorf("failed to append proxy cert data to cert pool %s", pkcp.CertPath)
162164
}
163165

164166
transport := &http.Transport{
@@ -175,16 +177,16 @@ func (e *E2E) newIssuerProxyPair() (*http.Transport, error) {
175177

176178
cmd := exec.Command("../../kube-oidc-proxy",
177179
fmt.Sprintf("--oidc-issuer-url=https://127.0.0.1:%s", issuer.Port()),
178-
fmt.Sprintf("--oidc-ca-file=%s", issuer.CertPath()),
180+
fmt.Sprintf("--oidc-ca-file=%s", e.issuer.KeyCertPair().CertPath),
179181
"--oidc-client-id=kube-oidc-proxy_e2e_client-id",
180182
"--oidc-username-claim=e2e-username-claim",
181183
"--oidc-groups-claim=e2e-groups-claim",
182184
"--oidc-signing-algs=RS256",
183185

184186
"--bind-address=127.0.0.1",
185-
fmt.Sprintf("--secure-port=%s", proxyPort),
186-
fmt.Sprintf("--tls-cert-file=%s", proxyCertPath),
187-
fmt.Sprintf("--tls-private-key-file=%s", proxyKeyPath),
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),
188190

189191
fmt.Sprintf("--kubeconfig=%s", e.kubeKubeconfig),
190192
)
@@ -245,7 +247,7 @@ func (e *E2E) proxyRestClient() (*rest.Config, error) {
245247
},
246248
},
247249
TLSClientConfig: rest.TLSClientConfig{
248-
CAData: e.proxyCert,
250+
CAData: e.proxyKeyCertPair.Cert,
249251
},
250252

251253
APIPath: "/api",
@@ -265,5 +267,47 @@ func (e *E2E) cleanup() {
265267
err)
266268
}
267269

270+
e.proxyCmd = nil
271+
}
272+
}
273+
274+
func (e *E2E) runProxy(extraArgs ...string) error {
275+
if e.issuer == nil {
276+
return errors.New("failed to run proxy: issuer not ready")
277+
}
278+
279+
args := append(
280+
[]string{
281+
fmt.Sprintf("--oidc-issuer-url=https://127.0.0.1:%s", e.issuer.Port()),
282+
fmt.Sprintf("--oidc-ca-file=%s", e.issuer.KeyCertPair().CertPath),
283+
"--oidc-client-id=kube-oidc-proxy_e2e_client-id",
284+
"--oidc-username-claim=e2e-username-claim",
285+
"--oidc-groups-claim=e2e-groups-claim",
286+
"--oidc-signing-algs=RS256",
287+
"--v=5",
288+
289+
"--bind-address=127.0.0.1",
290+
fmt.Sprintf("--secure-port=%s", e.proxyPort),
291+
fmt.Sprintf("--tls-cert-file=%s", e.proxyKeyCertPair.CertPath),
292+
fmt.Sprintf("--tls-private-key-file=%s", e.proxyKeyCertPair.KeyPath),
293+
294+
fmt.Sprintf("--kubeconfig=%s", e.kubeKubeconfig),
295+
},
296+
extraArgs...,
297+
)
298+
299+
cmd := exec.Command("../../kube-oidc-proxy", args...)
300+
301+
cmd.Stderr = os.Stderr
302+
cmd.Stdout = os.Stdout
303+
if err := cmd.Start(); err != nil {
304+
return err
268305
}
306+
307+
e.proxyCmd = cmd
308+
309+
// wait more than enough for proxy to become ready
310+
time.Sleep(time.Second * 13)
311+
312+
return nil
269313
}

pkg/e2e/issuer/issuer.go

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22
package issuer
33

44
import (
5-
"crypto/rsa"
65
"encoding/base64"
76
"fmt"
87
"net/http"
98
"time"
109

11-
"github.com/jetstack/kube-oidc-proxy/pkg/util"
1210
"k8s.io/klog"
11+
12+
"github.com/jetstack/kube-oidc-proxy/pkg/util"
1313
)
1414

1515
type Issuer struct {
16-
tlsDir string
17-
listenPort string
18-
certPath, keyPath string
16+
tlsDir string
17+
listenPort string
1918

20-
sk *rsa.PrivateKey
19+
keyCertPair *util.KeyCertPair
2120
}
2221

2322
func New(tlsDir string) *Issuer {
@@ -33,18 +32,16 @@ func (i *Issuer) Run() error {
3332
}
3433
i.listenPort = listenPort
3534

36-
certPath, keyPath, sk, _, err := util.NewTLSSelfSignedCertKey(i.tlsDir, "oidc-issuer")
35+
kcp, err := util.NewTLSSelfSignedCertKey(i.tlsDir, "oidc-issuer")
3736
if err != nil {
3837
return fmt.Errorf("failed to create issuer key pair: %s", err)
3938
}
40-
i.certPath = certPath
41-
i.keyPath = keyPath
42-
i.sk = sk
39+
i.keyCertPair = kcp
4340

4441
serveAddr := fmt.Sprintf("127.0.0.1:%s", i.listenPort)
4542

4643
go func() {
47-
err = http.ListenAndServeTLS(serveAddr, i.certPath, i.keyPath, i)
44+
err = http.ListenAndServeTLS(serveAddr, i.keyCertPair.CertPath, i.keyCertPair.KeyPath, i)
4845
if err != nil {
4946
klog.Errorf("failed to server secure tls: %s", err)
5047
}
@@ -80,16 +77,8 @@ func (i *Issuer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
8077
}
8178
}
8279

83-
func (i *Issuer) CertPath() string {
84-
return i.certPath
85-
}
86-
87-
func (i *Issuer) KeyPath() string {
88-
return i.keyPath
89-
}
90-
91-
func (i *Issuer) Key() *rsa.PrivateKey {
92-
return i.sk
80+
func (i *Issuer) KeyCertPair() *util.KeyCertPair {
81+
return i.keyCertPair
9382
}
9483

9584
func (i *Issuer) Port() string {
@@ -128,7 +117,7 @@ func (i *Issuer) wellKnownResponse() []byte {
128117
}
129118

130119
func (i *Issuer) CertsDisc() []byte {
131-
n := base64.RawURLEncoding.EncodeToString(i.sk.N.Bytes())
120+
n := base64.RawURLEncoding.EncodeToString(i.keyCertPair.Key.N.Bytes())
132121

133122
return []byte(fmt.Sprintf(`{
134123
"keys": [

0 commit comments

Comments
 (0)