mirror of
https://github.com/int128/kubelogin.git
synced 2026-04-22 09:16:37 +00:00
Cache oidc.Provider to reduce discovery requests (#107)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user