diff --git a/lib/client/api.go b/lib/client/api.go index 8f4b13c4bd101..ac3f5e9c2297a 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -4155,9 +4155,9 @@ func (tc *TeleportClient) Ping(ctx context.Context) (*webclient.PingResponse, er return nil, trace.Wrap(err) } - // If version checking was requested and the server advertises a minimum version. - if tc.CheckVersions && pr.MinClientVersion != "" { - if err := utils.CheckVersion(teleport.Version, pr.MinClientVersion); err != nil && trace.IsBadParameter(err) { + // Verify server->client and client->server compatibility. + if tc.CheckVersions { + if !utils.MeetsVersion(teleport.Version, pr.MinClientVersion) { fmt.Fprintf(tc.Stderr, ` WARNING Detected potentially incompatible client and server versions. @@ -4166,6 +4166,18 @@ func (tc *TeleportClient) Ping(ctx context.Context) (*webclient.PingResponse, er Future versions of tsh will fail when incompatible versions are detected. `, pr.MinClientVersion, teleport.Version, pr.MinClientVersion) } + + // Recent `tsh mfa` changes require at least Teleport v15. + const minServerVersion = "15.0.0-aa" // "-aa" matches all development versions + if !utils.MeetsVersion(pr.ServerVersion, minServerVersion) { + fmt.Fprintf(tc.Stderr, ` + WARNING + Detected incompatible client and server versions. + Minimum server version supported by tsh is %v but your server is using %v. + Please use a tsh version that matches your server. + You may use the --skip-version-check flag to bypass this check. + `, minServerVersion, pr.ServerVersion) + } } // Update tc with proxy and auth settings specified in Ping response. diff --git a/lib/utils/utils_test.go b/lib/utils/utils_test.go index a56e560cc1605..85982c6e15262 100644 --- a/lib/utils/utils_test.go +++ b/lib/utils/utils_test.go @@ -149,6 +149,7 @@ func TestVersions(t *testing.T) { for _, testCase := range successTestCases { t.Run(testCase.info, func(t *testing.T) { require.NoError(t, CheckVersion(testCase.client, testCase.minClient)) + assert.True(t, MeetsVersion(testCase.client, testCase.minClient), "MeetsVersion expected to succeed") }) } @@ -159,6 +160,7 @@ func TestVersions(t *testing.T) { for _, testCase := range failTestCases { t.Run(testCase.info, func(t *testing.T) { fixtures.AssertBadParameter(t, CheckVersion(testCase.client, testCase.minClient)) + assert.False(t, MeetsVersion(testCase.client, testCase.minClient), "MeetsVersion expected to fail") }) } } diff --git a/lib/utils/ver.go b/lib/utils/ver.go index 42c8d8263c1d6..34f796853e23a 100644 --- a/lib/utils/ver.go +++ b/lib/utils/ver.go @@ -21,6 +21,18 @@ import ( "github.com/gravitational/trace" ) +// MeetsVersion returns true if gotVer is empty or at least minVer. +func MeetsVersion(gotVer, minVer string) bool { + if gotVer == "" { + return true // Ignore empty versions. + } + + err := CheckVersion(gotVer, minVer) + + // Non BadParameter errors are semver parsing errors. + return !trace.IsBadParameter(err) +} + // CheckVersion compares a version with a minimum version supported. func CheckVersion(currentVersion, minVersion string) error { currentSemver, minSemver, err := versionStringToSemver(currentVersion, minVersion) diff --git a/lib/utils/ver_test.go b/lib/utils/ver_test.go index 56fa2a288bdf2..a78c0e30b96cc 100644 --- a/lib/utils/ver_test.go +++ b/lib/utils/ver_test.go @@ -18,6 +18,21 @@ package utils import "testing" +func TestMeetsVersion_emptyOrInvalid(t *testing.T) { + // See TestVersions for more comprehensive tests. + + if !MeetsVersion("", "v1.2.3") { + t.Error("MeetsVersion with an empty gotVer should always succeed") + } + + if !MeetsVersion("banana", "v1.2.3") { + t.Error("MeetsVersion with an invalid version should always succeed") + } + if !MeetsVersion("v1.2.3", "banana") { + t.Error("MeetsVersion with an invalid version should always succeed") + } +} + func TestMinVerWithoutPreRelease(t *testing.T) { t.Parallel()