mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 01:06:27 +00:00
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
This commit is contained in:
parent
e85940ed17
commit
94ffa344a8
@ -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 (
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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) {
|
|||||||
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 {
|
if err != nil {
|
||||||
return jose.JWT{}, err
|
return jose.JWT{}, fmt.Errorf("could not refresh token: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, rt)
|
|
||||||
if err != nil {
|
|
||||||
return jose.JWT{}, 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) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user