diff --git a/api/client/errors.go b/api/client/errors.go new file mode 100644 index 0000000000000..69ee906467885 --- /dev/null +++ b/api/client/errors.go @@ -0,0 +1,21 @@ +// Copyright 2024 Gravitational, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import "github.com/gravitational/trace" + +// ErrClientCredentialsHaveExpired means that the credentials expired on +// the server-side and the user should relogin. +var ErrClientCredentialsHaveExpired = &trace.AccessDeniedError{Message: "access denied: client credentials have expired, please relogin."} diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index dcf0dad828e6e..294d1bf33b7d9 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -2898,7 +2898,7 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC return nil, trace.AccessDenied("access denied") } if req.Expires.Before(a.authServer.GetClock().Now()) { - return nil, trace.AccessDenied("access denied: client credentials have expired, please relogin.") + return nil, trace.Wrap(client.ErrClientCredentialsHaveExpired) } if identity.Renewable || isRoleImpersonation(req) { diff --git a/lib/client/api.go b/lib/client/api.go index 2d050e40c17aa..373853aa06304 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -679,7 +679,17 @@ func IsErrorResolvableWithRelogin(err error) bool { // https://github.com/gravitational/teleport/pull/30578. var remoteErr *interceptors.RemoteError if errors.As(err, &remoteErr) { - return false + // Exception for the two "retryable" errors that come from RPCs. + // + // Since Connect no longer checks the user cert before making an RPC, + // it has to be able to properly recognize "expired certs" errors + // that come from the server (to show a re-login dialog). + // + // TODO(gzdunek): These manual checks should be replaced with retryable + // errors returned explicitly, as described below by codingllama. + isClientCredentialsHaveExpired := errors.Is(err, client.ErrClientCredentialsHaveExpired) + isTLSExpiredCertificate := strings.Contains(err.Error(), "tls: expired certificate") + return isClientCredentialsHaveExpired || isTLSExpiredCertificate } return keys.IsPrivateKeyPolicyError(err) ||