Cache oidc.Provider to reduce discovery requests (#107)

This commit is contained in:
Hidetake Iwata
2019-06-26 10:16:10 +09:00
committed by GitHub
parent 10c7b6a84f
commit 391754e1ce
7 changed files with 57 additions and 73 deletions

View File

@@ -19,7 +19,7 @@ type Kubeconfig interface {
}
type OIDC interface {
New(config OIDCClientConfig) (OIDCClient, error)
New(ctx context.Context, config OIDCClientConfig) (OIDCClient, error)
}
type OIDCClientConfig struct {
@@ -35,14 +35,12 @@ type OIDCClient interface {
}
type OIDCAuthenticateByCodeIn struct {
Config kubeconfig.OIDCConfig
LocalServerPort []int // HTTP server port candidates
SkipOpenBrowser bool // skip opening browser if true
ShowLocalServerURL interface{ ShowLocalServerURL(url string) }
}
type OIDCAuthenticateByPasswordIn struct {
Config kubeconfig.OIDCConfig
Username string
Password string
}
@@ -54,7 +52,7 @@ type OIDCAuthenticateOut struct {
}
type OIDCVerifyIn struct {
Config kubeconfig.OIDCConfig
IDToken string
}
type Env interface {

View File

@@ -85,16 +85,16 @@ func (m *MockOIDC) EXPECT() *MockOIDCMockRecorder {
}
// New mocks base method
func (m *MockOIDC) New(arg0 adaptors.OIDCClientConfig) (adaptors.OIDCClient, error) {
ret := m.ctrl.Call(m, "New", arg0)
func (m *MockOIDC) New(arg0 context.Context, arg1 adaptors.OIDCClientConfig) (adaptors.OIDCClient, error) {
ret := m.ctrl.Call(m, "New", arg0, arg1)
ret0, _ := ret[0].(adaptors.OIDCClient)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// New indicates an expected call of New
func (mr *MockOIDCMockRecorder) New(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockOIDC)(nil).New), arg0)
func (mr *MockOIDCMockRecorder) New(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockOIDC)(nil).New), arg0, arg1)
}
// MockOIDCClient is a mock of OIDCClient interface

View File

@@ -24,7 +24,7 @@ type Factory struct {
Logger adaptors.Logger
}
func (f *Factory) New(config adaptors.OIDCClientConfig) (adaptors.OIDCClient, error) {
func (f *Factory) New(ctx context.Context, config adaptors.OIDCClientConfig) (adaptors.OIDCClient, error) {
tlsConfig, err := tls.NewConfig(config, f.Logger)
if err != nil {
return nil, xerrors.Errorf("could not initialize TLS config: %w", err)
@@ -37,31 +37,39 @@ func (f *Factory) New(config adaptors.OIDCClientConfig) (adaptors.OIDCClient, er
Base: baseTransport,
Logger: f.Logger,
}
hc := &http.Client{
httpClient := &http.Client{
Transport: loggingTransport,
}
return &Client{hc}, nil
}
type Client struct {
hc *http.Client
}
func (c *Client) AuthenticateByCode(ctx context.Context, in adaptors.OIDCAuthenticateByCodeIn) (*adaptors.OIDCAuthenticateOut, error) {
if c.hc != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.hc)
}
provider, err := oidc.NewProvider(ctx, in.Config.IDPIssuerURL)
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
provider, err := oidc.NewProvider(ctx, config.Config.IDPIssuerURL)
if err != nil {
return nil, xerrors.Errorf("could not discovery the OIDC issuer: %w", err)
}
config := oauth2cli.Config{
OAuth2Config: oauth2.Config{
return &client{
httpClient: httpClient,
provider: provider,
oauth2Config: oauth2.Config{
Endpoint: provider.Endpoint(),
ClientID: in.Config.ClientID,
ClientSecret: in.Config.ClientSecret,
Scopes: append(in.Config.ExtraScopes, oidc.ScopeOpenID),
ClientID: config.Config.ClientID,
ClientSecret: config.Config.ClientSecret,
Scopes: append(config.Config.ExtraScopes, oidc.ScopeOpenID),
},
}, nil
}
type client struct {
httpClient *http.Client
provider *oidc.Provider
oauth2Config oauth2.Config
}
func (c *client) AuthenticateByCode(ctx context.Context, in adaptors.OIDCAuthenticateByCodeIn) (*adaptors.OIDCAuthenticateOut, error) {
if c.httpClient != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
}
config := oauth2cli.Config{
OAuth2Config: c.oauth2Config,
LocalServerPort: in.LocalServerPort,
SkipOpenBrowser: in.SkipOpenBrowser,
AuthCodeOptions: []oauth2.AuthCodeOption{oauth2.AccessTypeOffline},
@@ -75,7 +83,7 @@ func (c *Client) AuthenticateByCode(ctx context.Context, in adaptors.OIDCAuthent
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := provider.Verifier(&oidc.Config{ClientID: in.Config.ClientID})
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
@@ -87,21 +95,11 @@ func (c *Client) AuthenticateByCode(ctx context.Context, in adaptors.OIDCAuthent
}, nil
}
func (c *Client) AuthenticateByPassword(ctx context.Context, in adaptors.OIDCAuthenticateByPasswordIn) (*adaptors.OIDCAuthenticateOut, error) {
if c.hc != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.hc)
func (c *client) AuthenticateByPassword(ctx context.Context, in adaptors.OIDCAuthenticateByPasswordIn) (*adaptors.OIDCAuthenticateOut, error) {
if c.httpClient != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
}
provider, err := oidc.NewProvider(ctx, in.Config.IDPIssuerURL)
if err != nil {
return nil, xerrors.Errorf("could not discovery the OIDC issuer: %w", err)
}
config := oauth2.Config{
Endpoint: provider.Endpoint(),
ClientID: in.Config.ClientID,
ClientSecret: in.Config.ClientSecret,
Scopes: append(in.Config.ExtraScopes, oidc.ScopeOpenID),
}
token, err := config.PasswordCredentialsToken(ctx, in.Username, in.Password)
token, err := c.oauth2Config.PasswordCredentialsToken(ctx, in.Username, in.Password)
if err != nil {
return nil, xerrors.Errorf("could not get a token: %w", err)
}
@@ -109,7 +107,7 @@ func (c *Client) AuthenticateByPassword(ctx context.Context, in adaptors.OIDCAut
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := provider.Verifier(&oidc.Config{ClientID: in.Config.ClientID})
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
@@ -121,16 +119,12 @@ func (c *Client) AuthenticateByPassword(ctx context.Context, in adaptors.OIDCAut
}, nil
}
func (c *Client) Verify(ctx context.Context, in adaptors.OIDCVerifyIn) (*oidc.IDToken, error) {
if c.hc != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.hc)
func (c *client) Verify(ctx context.Context, in adaptors.OIDCVerifyIn) (*oidc.IDToken, error) {
if c.httpClient != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
}
provider, err := oidc.NewProvider(ctx, in.Config.IDPIssuerURL)
if err != nil {
return nil, xerrors.Errorf("could not discovery the OIDC issuer: %w", err)
}
verifier := provider.Verifier(&oidc.Config{ClientID: in.Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, in.Config.IDToken)
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, in.IDToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
}

View File

@@ -42,7 +42,7 @@ func (u *Exec) doInternal(ctx context.Context, in usecases.LoginIn) error {
u.Logger.Debugf(1, "Using the authentication provider of the user %s", auth.UserName)
u.Logger.Debugf(1, "A token will be written to %s", auth.LocationOfOrigin)
client, err := u.OIDC.New(adaptors.OIDCClientConfig{
client, err := u.OIDC.New(ctx, adaptors.OIDCClientConfig{
Config: auth.OIDCConfig,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
@@ -53,7 +53,7 @@ func (u *Exec) doInternal(ctx context.Context, in usecases.LoginIn) error {
if auth.OIDCConfig.IDToken != "" {
u.Logger.Debugf(1, "Found the ID token in the kubeconfig")
token, err := client.Verify(ctx, adaptors.OIDCVerifyIn{Config: auth.OIDCConfig})
token, err := client.Verify(ctx, adaptors.OIDCVerifyIn{IDToken: auth.OIDCConfig.IDToken})
if err == nil {
u.Logger.Debugf(1, "You already have a valid token until %s", token.Expiry)
dumpIDToken(u.Logger, token)
@@ -71,7 +71,6 @@ func (u *Exec) doInternal(ctx context.Context, in usecases.LoginIn) error {
}
}
out, err := client.AuthenticateByPassword(ctx, adaptors.OIDCAuthenticateByPasswordIn{
Config: auth.OIDCConfig,
Username: in.Username,
Password: in.Password,
})
@@ -81,7 +80,6 @@ func (u *Exec) doInternal(ctx context.Context, in usecases.LoginIn) error {
tokenSet = out
} else {
out, err := client.AuthenticateByCode(ctx, adaptors.OIDCAuthenticateByCodeIn{
Config: auth.OIDCConfig,
LocalServerPort: in.ListenPort,
SkipOpenBrowser: in.SkipOpenBrowser,
ShowLocalServerURL: u.ShowLocalServerURL,

View File

@@ -28,9 +28,8 @@ func TestExec_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
New(ctx, adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
Return(newMockCodeOIDC(ctrl, ctx, adaptors.OIDCAuthenticateByCodeIn{
Config: auth.OIDCConfig,
LocalServerPort: []int{10000},
}), nil)

View File

@@ -56,7 +56,7 @@ func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
u.Logger.Debugf(1, "Using the authentication provider of the user %s", auth.UserName)
u.Logger.Debugf(1, "A token will be written to %s", auth.LocationOfOrigin)
client, err := u.OIDC.New(adaptors.OIDCClientConfig{
client, err := u.OIDC.New(ctx, adaptors.OIDCClientConfig{
Config: auth.OIDCConfig,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
@@ -67,7 +67,7 @@ func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
if auth.OIDCConfig.IDToken != "" {
u.Logger.Debugf(1, "Found the ID token in the kubeconfig")
token, err := client.Verify(ctx, adaptors.OIDCVerifyIn{Config: auth.OIDCConfig})
token, err := client.Verify(ctx, adaptors.OIDCVerifyIn{IDToken: auth.OIDCConfig.IDToken})
if err == nil {
u.Logger.Printf("You already have a valid token until %s", token.Expiry)
dumpIDToken(u.Logger, token)
@@ -85,7 +85,6 @@ func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
}
}
out, err := client.AuthenticateByPassword(ctx, adaptors.OIDCAuthenticateByPasswordIn{
Config: auth.OIDCConfig,
Username: in.Username,
Password: in.Password,
})
@@ -95,7 +94,6 @@ func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
tokenSet = out
} else {
out, err := client.AuthenticateByCode(ctx, adaptors.OIDCAuthenticateByCodeIn{
Config: auth.OIDCConfig,
LocalServerPort: in.ListenPort,
SkipOpenBrowser: in.SkipOpenBrowser,
ShowLocalServerURL: u.ShowLocalServerURL,

View File

@@ -68,9 +68,8 @@ func TestLogin_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
New(ctx, adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
Return(newMockCodeOIDC(ctrl, ctx, adaptors.OIDCAuthenticateByCodeIn{
Config: auth.OIDCConfig,
LocalServerPort: []int{10000},
}), nil)
@@ -101,13 +100,12 @@ func TestLogin_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(adaptors.OIDCClientConfig{
New(ctx, adaptors.OIDCClientConfig{
Config: auth.OIDCConfig,
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(newMockPasswordOIDC(ctrl, ctx, adaptors.OIDCAuthenticateByPasswordIn{
Config: auth.OIDCConfig,
Username: "USER",
Password: "PASS",
}), nil)
@@ -145,9 +143,8 @@ func TestLogin_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
New(ctx, adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
Return(newMockPasswordOIDC(ctrl, ctx, adaptors.OIDCAuthenticateByPasswordIn{
Config: auth.OIDCConfig,
Username: "USER",
Password: "PASS",
}), nil)
@@ -181,7 +178,7 @@ func TestLogin_Do(t *testing.T) {
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
New(ctx, adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
Return(mock_adaptors.NewMockOIDCClient(ctrl), nil)
mockEnv := mock_adaptors.NewMockEnv(ctrl)
@@ -213,11 +210,11 @@ func TestLogin_Do(t *testing.T) {
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
mockOIDCClient.EXPECT().
Verify(ctx, adaptors.OIDCVerifyIn{Config: auth.OIDCConfig}).
Verify(ctx, adaptors.OIDCVerifyIn{IDToken: auth.OIDCConfig.IDToken}).
Return(&oidc.IDToken{Expiry: time.Now()}, nil)
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
New(ctx, adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
Return(mockOIDCClient, nil)
u := Login{
@@ -243,13 +240,13 @@ func TestLogin_Do(t *testing.T) {
mockKubeconfig.EXPECT().
UpdateAuth(newAuth("YOUR_ID_TOKEN", "YOUR_REFRESH_TOKEN"))
mockOIDCClient := newMockCodeOIDC(ctrl, ctx, adaptors.OIDCAuthenticateByCodeIn{Config: auth.OIDCConfig})
mockOIDCClient := newMockCodeOIDC(ctrl, ctx, adaptors.OIDCAuthenticateByCodeIn{})
mockOIDCClient.EXPECT().
Verify(ctx, adaptors.OIDCVerifyIn{Config: auth.OIDCConfig}).
Verify(ctx, adaptors.OIDCVerifyIn{IDToken: auth.OIDCConfig.IDToken}).
Return(nil, xerrors.New("token expired"))
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
New(ctx, adaptors.OIDCClientConfig{Config: auth.OIDCConfig}).
Return(mockOIDCClient, nil)
u := Login{