Commit 94ffa344 authored by Bobby Rullo's avatar Bobby Rullo

OIDC authprovider more testable, and add backoff

* Use an interface for OIDC Client, so that we're testing the behavior of the client, not the go-oidc package itself * add backoff and retry when server rejects token
parent e85940ed
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/util/wait"
) )
const ( const (
...@@ -43,6 +44,15 @@ const ( ...@@ -43,6 +44,15 @@ const (
cfgRefreshToken = "refresh-token" cfgRefreshToken = "refresh-token"
) )
var (
backoff = wait.Backoff{
Duration: 1 * time.Second,
Factor: 2,
Jitter: .1,
Steps: 5,
}
)
func init() { func init() {
if err := restclient.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil { if err := restclient.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil {
glog.Fatalf("Failed to register oidc auth plugin: %v", err) glog.Fatalf("Failed to register oidc auth plugin: %v", err)
...@@ -100,7 +110,7 @@ func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.A ...@@ -100,7 +110,7 @@ func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.A
Secret: clientSecret, Secret: clientSecret,
}, },
ProviderConfig: providerCfg, ProviderConfig: providerCfg,
Scope: scopes, Scope: append(scopes, oidc.DefaultScope...),
} }
client, err := oidc.NewClient(oidcCfg) client, err := oidc.NewClient(oidcCfg)
...@@ -108,6 +118,8 @@ func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.A ...@@ -108,6 +118,8 @@ func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.A
return nil, fmt.Errorf("error creating OIDC Client: %v", err) return nil, fmt.Errorf("error creating OIDC Client: %v", err)
} }
oClient := &oidcClient{client}
var initialIDToken jose.JWT var initialIDToken jose.JWT
if cfg[cfgIDToken] != "" { if cfg[cfgIDToken] != "" {
initialIDToken, err = jose.ParseJWT(cfg[cfgIDToken]) initialIDToken, err = jose.ParseJWT(cfg[cfgIDToken])
...@@ -119,7 +131,7 @@ func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.A ...@@ -119,7 +131,7 @@ func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.A
return &oidcAuthProvider{ return &oidcAuthProvider{
initialIDToken: initialIDToken, initialIDToken: initialIDToken,
refresher: &idTokenRefresher{ refresher: &idTokenRefresher{
client: client, client: oClient,
cfg: cfg, cfg: cfg,
persister: persister, persister: persister,
}, },
...@@ -137,16 +149,57 @@ func (g *oidcAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper ...@@ -137,16 +149,57 @@ func (g *oidcAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper
RoundTripper: rt, RoundTripper: rt,
} }
at.SetJWT(g.initialIDToken) at.SetJWT(g.initialIDToken)
return at return &roundTripper{
wrapped: at,
refresher: g.refresher,
}
} }
func (g *oidcAuthProvider) Login() error { func (g *oidcAuthProvider) Login() error {
return errors.New("not yet implemented") return errors.New("not yet implemented")
} }
type OIDCClient interface {
refreshToken(rt string) (oauth2.TokenResponse, error)
verifyJWT(jwt jose.JWT) error
}
type roundTripper struct {
refresher *idTokenRefresher
wrapped *oidc.AuthenticatedTransport
}
func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
var res *http.Response
var err error
firstTime := true
wait.ExponentialBackoff(backoff, func() (bool, error) {
if !firstTime {
var jwt jose.JWT
jwt, err = r.refresher.Refresh()
if err != nil {
return true, nil
}
r.wrapped.SetJWT(jwt)
} else {
firstTime = false
}
res, err = r.wrapped.RoundTrip(req)
if err != nil {
return true, nil
}
if res.StatusCode == http.StatusUnauthorized {
return false, nil
}
return true, nil
})
return res, err
}
type idTokenRefresher struct { type idTokenRefresher struct {
cfg map[string]string cfg map[string]string
client *oidc.Client client OIDCClient
persister restclient.AuthProviderConfigPersister persister restclient.AuthProviderConfigPersister
intialIDToken jose.JWT intialIDToken jose.JWT
} }
...@@ -177,16 +230,10 @@ func (r *idTokenRefresher) Refresh() (jose.JWT, error) { ...@@ -177,16 +230,10 @@ func (r *idTokenRefresher) Refresh() (jose.JWT, error) {
return jose.JWT{}, errors.New("No valid id-token, and cannot refresh without refresh-token") return jose.JWT{}, errors.New("No valid id-token, and cannot refresh without refresh-token")
} }
oac, err := r.client.OAuthClient() tokens, err := r.client.refreshToken(rt)
if err != nil {
return jose.JWT{}, err
}
tokens, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, rt)
if err != nil { if err != nil {
return jose.JWT{}, err return jose.JWT{}, fmt.Errorf("could not refresh token: %v", err)
} }
jwt, err := jose.ParseJWT(tokens.IDToken) jwt, err := jose.ParseJWT(tokens.IDToken)
if err != nil { if err != nil {
return jose.JWT{}, err return jose.JWT{}, err
...@@ -202,5 +249,22 @@ func (r *idTokenRefresher) Refresh() (jose.JWT, error) { ...@@ -202,5 +249,22 @@ func (r *idTokenRefresher) Refresh() (jose.JWT, error) {
return jose.JWT{}, fmt.Errorf("could not perist new tokens: %v", err) return jose.JWT{}, fmt.Errorf("could not perist new tokens: %v", err)
} }
return jwt, r.client.VerifyJWT(jwt) return jwt, r.client.verifyJWT(jwt)
}
type oidcClient struct {
client *oidc.Client
}
func (o *oidcClient) refreshToken(rt string) (oauth2.TokenResponse, error) {
oac, err := o.client.OAuthClient()
if err != nil {
return oauth2.TokenResponse{}, err
}
return oac.RequestToken(oauth2.GrantTypeRefreshToken, rt)
}
func (o *oidcClient) verifyJWT(jwt jose.JWT) error {
return o.client.VerifyJWT(jwt)
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment