From 3a2aa0c6c09a8fcd9988db3f4a02c5b21ceed734 Mon Sep 17 00:00:00 2001 From: Hidetake Iwata Date: Thu, 1 Aug 2019 10:19:58 +0900 Subject: [PATCH] Fix TLS certificate on refreshing token (#125) * Add test of refreshing token with TLS cert * Fix TLS certificate on refreshing token (#123) --- adaptors/oidc/oidc.go | 18 ++- e2e_test/login_test.go | 352 +++++++++++++++++++---------------------- 2 files changed, 176 insertions(+), 194 deletions(-) diff --git a/adaptors/oidc/oidc.go b/adaptors/oidc/oidc.go index 08f3eb2a..0a9b7039 100644 --- a/adaptors/oidc/oidc.go +++ b/adaptors/oidc/oidc.go @@ -71,11 +71,16 @@ type client struct { logger adaptors.Logger } -// AuthenticateByCode performs the authorization code flow. -func (c *client) AuthenticateByCode(ctx context.Context, in adaptors.OIDCAuthenticateByCodeIn) (*adaptors.OIDCAuthenticateOut, error) { +func (c *client) wrapContext(ctx context.Context) context.Context { if c.httpClient != nil { ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) } + return ctx +} + +// AuthenticateByCode performs the authorization code flow. +func (c *client) AuthenticateByCode(ctx context.Context, in adaptors.OIDCAuthenticateByCodeIn) (*adaptors.OIDCAuthenticateOut, error) { + ctx = c.wrapContext(ctx) nonce, err := newNonce() if err != nil { return nil, xerrors.Errorf("could not generate a nonce parameter") @@ -125,9 +130,7 @@ func newNonce() (string, error) { // AuthenticateByPassword performs the resource owner password credentials flow. 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) - } + ctx = c.wrapContext(ctx) token, err := c.oauth2Config.PasswordCredentialsToken(ctx, in.Username, in.Password) if err != nil { return nil, xerrors.Errorf("could not get a token: %w", err) @@ -156,9 +159,7 @@ func (c *client) AuthenticateByPassword(ctx context.Context, in adaptors.OIDCAut // Verify checks client ID and signature of the ID token. // This does not check the expiration and caller should check it. func (c *client) Verify(ctx context.Context, in adaptors.OIDCVerifyIn) (*adaptors.OIDCVerifyOut, error) { - if c.httpClient != nil { - ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) - } + ctx = c.wrapContext(ctx) verifier := c.provider.Verifier(&oidc.Config{ ClientID: c.oauth2Config.ClientID, SkipExpiryCheck: true, @@ -179,6 +180,7 @@ func (c *client) Verify(ctx context.Context, in adaptors.OIDCVerifyIn) (*adaptor // Refresh sends a refresh token request and returns a token set. func (c *client) Refresh(ctx context.Context, in adaptors.OIDCRefreshIn) (*adaptors.OIDCAuthenticateOut, error) { + ctx = c.wrapContext(ctx) currentToken := &oauth2.Token{ Expiry: time.Now(), RefreshToken: in.RefreshToken, diff --git a/e2e_test/login_test.go b/e2e_test/login_test.go index aa70f098..a9a77d53 100644 --- a/e2e_test/login_test.go +++ b/e2e_test/login_test.go @@ -36,56 +36,181 @@ var ( func TestCmd_Run_Login(t *testing.T) { timeout := 1 * time.Second - t.Run("Defaults", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() + type testParameter struct { + startServer func(t *testing.T, h http.Handler) (string, localserver.Shutdowner) + kubeconfigIDPCertificateAuthority string + clientTLSConfig *tls.Config + } - service := mock_idp.NewMockService(ctrl) - serverURL, server := localserver.Start(t, idp.NewHandler(t, service)) - defer server.Shutdown(t, ctx) - var idToken string - setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken) + testParameters := map[string]testParameter{ + "NoTLS": { + startServer: localserver.Start, + }, + "CACert": { + startServer: func(t *testing.T, h http.Handler) (string, localserver.Shutdowner) { + return localserver.StartTLS(t, keys.TLSServerCert, keys.TLSServerKey, h) + }, + kubeconfigIDPCertificateAuthority: keys.TLSCACert, + clientTLSConfig: keys.TLSCACertAsConfig, + }, + } - kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{Issuer: serverURL}) - defer os.Remove(kubeConfigFilename) + runTest := func(t *testing.T, p testParameter) { + t.Run("Defaults", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() - req := startBrowserRequest(t, ctx, nil) - runCmd(t, ctx, req, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "0") - req.wait() - kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ - IDToken: idToken, - RefreshToken: "YOUR_REFRESH_TOKEN", + service := mock_idp.NewMockService(ctrl) + serverURL, server := p.startServer(t, idp.NewHandler(t, service)) + defer server.Shutdown(t, ctx) + var idToken string + setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken) + + kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ + Issuer: serverURL, + IDPCertificateAuthority: p.kubeconfigIDPCertificateAuthority, + }) + defer os.Remove(kubeConfigFilename) + + req := startBrowserRequest(t, ctx, p.clientTLSConfig) + runCmd(t, ctx, req, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "0") + req.wait() + kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ + IDToken: idToken, + RefreshToken: "YOUR_REFRESH_TOKEN", + }) }) - }) - t.Run("ResourceOwnerPasswordCredentials", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() + t.Run("ResourceOwnerPasswordCredentials", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() - service := mock_idp.NewMockService(ctrl) - serverURL, server := localserver.Start(t, idp.NewHandler(t, service)) - defer server.Shutdown(t, ctx) - idToken := newIDToken(t, serverURL, "", tokenExpiryFuture) - service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL)) - service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair)) - service.EXPECT().AuthenticatePassword("USER", "PASS", "openid"). - Return(idp.NewTokenResponse(idToken, "YOUR_REFRESH_TOKEN"), nil) + service := mock_idp.NewMockService(ctrl) + serverURL, server := p.startServer(t, idp.NewHandler(t, service)) + defer server.Shutdown(t, ctx) + idToken := newIDToken(t, serverURL, "", tokenExpiryFuture) + service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL)) + service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair)) + service.EXPECT().AuthenticatePassword("USER", "PASS", "openid"). + Return(idp.NewTokenResponse(idToken, "YOUR_REFRESH_TOKEN"), nil) - kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{Issuer: serverURL}) - defer os.Remove(kubeConfigFilename) + kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ + Issuer: serverURL, + IDPCertificateAuthority: p.kubeconfigIDPCertificateAuthority, + }) + defer os.Remove(kubeConfigFilename) - runCmd(t, ctx, &nopBrowserRequest{t}, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--username", "USER", "--password", "PASS") - kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ - IDToken: idToken, - RefreshToken: "YOUR_REFRESH_TOKEN", + runCmd(t, ctx, &nopBrowserRequest{t}, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--username", "USER", "--password", "PASS") + kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ + IDToken: idToken, + RefreshToken: "YOUR_REFRESH_TOKEN", + }) }) - }) + + t.Run("HasValidToken", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + service := mock_idp.NewMockService(ctrl) + serverURL, server := p.startServer(t, idp.NewHandler(t, service)) + defer server.Shutdown(t, ctx) + idToken := newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryFuture) + service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL)) + service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair)) + + kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ + Issuer: serverURL, + IDToken: idToken, + RefreshToken: "YOUR_REFRESH_TOKEN", + IDPCertificateAuthority: p.kubeconfigIDPCertificateAuthority, + }) + defer os.Remove(kubeConfigFilename) + + runCmd(t, ctx, &nopBrowserRequest{t}, "--kubeconfig", kubeConfigFilename, "--skip-open-browser") + kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ + IDToken: idToken, + RefreshToken: "YOUR_REFRESH_TOKEN", + }) + }) + + t.Run("HasValidRefreshToken", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + service := mock_idp.NewMockService(ctrl) + serverURL, server := p.startServer(t, idp.NewHandler(t, service)) + defer server.Shutdown(t, ctx) + idToken := newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryFuture) + service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL)) + service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair)) + service.EXPECT().Refresh("VALID_REFRESH_TOKEN"). + Return(idp.NewTokenResponse(idToken, "NEW_REFRESH_TOKEN"), nil) + + kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ + Issuer: serverURL, + IDToken: newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryPast), // expired + RefreshToken: "VALID_REFRESH_TOKEN", + IDPCertificateAuthority: p.kubeconfigIDPCertificateAuthority, + }) + defer os.Remove(kubeConfigFilename) + + runCmd(t, ctx, &nopBrowserRequest{t}, "--kubeconfig", kubeConfigFilename, "--skip-open-browser") + kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ + IDToken: idToken, + RefreshToken: "NEW_REFRESH_TOKEN", + }) + }) + + t.Run("HasExpiredRefreshToken", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + service := mock_idp.NewMockService(ctrl) + serverURL, server := p.startServer(t, idp.NewHandler(t, service)) + defer server.Shutdown(t, ctx) + var idToken string + setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken) + service.EXPECT().Refresh("EXPIRED_REFRESH_TOKEN"). + Return(nil, &idp.ErrorResponse{Code: "invalid_request", Description: "token has expired"}). + MaxTimes(2) // package oauth2 will retry refreshing the token + + kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ + Issuer: serverURL, + IDToken: newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryPast), // expired + RefreshToken: "EXPIRED_REFRESH_TOKEN", + IDPCertificateAuthority: p.kubeconfigIDPCertificateAuthority, + }) + defer os.Remove(kubeConfigFilename) + + req := startBrowserRequest(t, ctx, p.clientTLSConfig) + runCmd(t, ctx, req, "--kubeconfig", kubeConfigFilename, "--skip-open-browser") + kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ + IDToken: idToken, + RefreshToken: "YOUR_REFRESH_TOKEN", + }) + }) + } + + for name, p := range testParameters { + t.Run(name, func(t *testing.T) { + runTest(t, p) + }) + } t.Run("env:KUBECONFIG", func(t *testing.T) { t.Parallel() @@ -141,151 +266,6 @@ func TestCmd_Run_Login(t *testing.T) { RefreshToken: "YOUR_REFRESH_TOKEN", }) }) - - t.Run("CACert", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - service := mock_idp.NewMockService(ctrl) - serverURL, server := localserver.StartTLS(t, keys.TLSServerCert, keys.TLSServerKey, idp.NewHandler(t, service)) - defer server.Shutdown(t, ctx) - var idToken string - setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken) - - kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ - Issuer: serverURL, - IDPCertificateAuthority: keys.TLSCACert, - }) - defer os.Remove(kubeConfigFilename) - - req := startBrowserRequest(t, ctx, keys.TLSCACertAsConfig) - runCmd(t, ctx, req, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "0") - req.wait() - kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ - IDToken: idToken, - RefreshToken: "YOUR_REFRESH_TOKEN", - }) - }) - - t.Run("CACertData", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - service := mock_idp.NewMockService(ctrl) - serverURL, server := localserver.StartTLS(t, keys.TLSServerCert, keys.TLSServerKey, idp.NewHandler(t, service)) - defer server.Shutdown(t, ctx) - var idToken string - setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken) - - kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ - Issuer: serverURL, - IDPCertificateAuthorityData: keys.TLSCACertAsBase64, - }) - defer os.Remove(kubeConfigFilename) - - req := startBrowserRequest(t, ctx, keys.TLSCACertAsConfig) - runCmd(t, ctx, req, "--kubeconfig", kubeConfigFilename, "--skip-open-browser", "--listen-port", "0") - req.wait() - kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ - IDToken: idToken, - RefreshToken: "YOUR_REFRESH_TOKEN", - }) - }) - - t.Run("HasValidToken", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - service := mock_idp.NewMockService(ctrl) - serverURL, server := localserver.Start(t, idp.NewHandler(t, service)) - defer server.Shutdown(t, ctx) - idToken := newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryFuture) - service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL)) - service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair)) - - kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ - Issuer: serverURL, - IDToken: idToken, - RefreshToken: "YOUR_REFRESH_TOKEN", - }) - defer os.Remove(kubeConfigFilename) - - runCmd(t, ctx, &nopBrowserRequest{t}, "--kubeconfig", kubeConfigFilename, "--skip-open-browser") - kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ - IDToken: idToken, - RefreshToken: "YOUR_REFRESH_TOKEN", - }) - }) - - t.Run("HasValidRefreshToken", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - service := mock_idp.NewMockService(ctrl) - serverURL, server := localserver.Start(t, idp.NewHandler(t, service)) - defer server.Shutdown(t, ctx) - idToken := newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryFuture) - service.EXPECT().Discovery().Return(idp.NewDiscoveryResponse(serverURL)) - service.EXPECT().GetCertificates().Return(idp.NewCertificatesResponse(keys.JWSKeyPair)) - service.EXPECT().Refresh("VALID_REFRESH_TOKEN"). - Return(idp.NewTokenResponse(idToken, "NEW_REFRESH_TOKEN"), nil) - - kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ - Issuer: serverURL, - IDToken: newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryPast), // expired - RefreshToken: "VALID_REFRESH_TOKEN", - }) - defer os.Remove(kubeConfigFilename) - - runCmd(t, ctx, &nopBrowserRequest{t}, "--kubeconfig", kubeConfigFilename, "--skip-open-browser") - kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ - IDToken: idToken, - RefreshToken: "NEW_REFRESH_TOKEN", - }) - }) - - t.Run("HasExpiredRefreshToken", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - service := mock_idp.NewMockService(ctrl) - serverURL, server := localserver.Start(t, idp.NewHandler(t, service)) - defer server.Shutdown(t, ctx) - var idToken string - setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken) - service.EXPECT().Refresh("EXPIRED_REFRESH_TOKEN"). - Return(nil, &idp.ErrorResponse{Code: "invalid_request", Description: "token has expired"}). - MaxTimes(2) // package oauth2 will retry refreshing the token - - kubeConfigFilename := kubeconfig.Create(t, &kubeconfig.Values{ - Issuer: serverURL, - IDToken: newIDToken(t, serverURL, "YOUR_NONCE", tokenExpiryPast), // expired - RefreshToken: "EXPIRED_REFRESH_TOKEN", - }) - defer os.Remove(kubeConfigFilename) - - req := startBrowserRequest(t, ctx, nil) - runCmd(t, ctx, req, "--kubeconfig", kubeConfigFilename, "--skip-open-browser") - kubeconfig.Verify(t, kubeConfigFilename, kubeconfig.AuthProviderConfig{ - IDToken: idToken, - RefreshToken: "YOUR_REFRESH_TOKEN", - }) - }) } func newIDToken(t *testing.T, issuer, nonce string, expiry time.Time) string {