From 61b2b938c9b4cc117d9bf9cd7b52b9f23d5539f9 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Thu, 19 Sep 2024 14:35:51 -0400 Subject: [PATCH 1/3] Expose client tools auto update for find endpoint --- api/client/webclient/webclient.go | 4 ++++ lib/web/apiserver.go | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index 2ccb27d0cb72b..249eb159c61bb 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -297,6 +297,10 @@ type PingResponse struct { ServerVersion string `json:"server_version"` // MinClientVersion is the minimum client version required by the server. MinClientVersion string `json:"min_client_version"` + // ToolsVersion defines the version of {tsh, tctl} for client auto update. + ToolsVersion string `json:"tools_version"` + // ToolsAutoupdate enables client auto update feature. + ToolsAutoupdate bool `json:"tools_autoupdate"` // ClusterName contains the name of the Teleport cluster. ClusterName string `json:"cluster_name"` diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index c53a988472851..62e6527b77a2f 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1516,7 +1516,7 @@ func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Para if err != nil { return nil, trace.Wrap(err) } - return webclient.PingResponse{ + response := webclient.PingResponse{ Auth: webclient.AuthenticationSettings{ // Nodes need the signature algorithm suite when joining to generate // keys with the correct algorithm. @@ -1526,7 +1526,23 @@ func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Para ServerVersion: teleport.Version, MinClientVersion: teleport.MinClientVersion, ClusterName: h.auth.clusterName, - }, nil + } + + autoUpdateConfig, err := h.GetAccessPoint().GetAutoUpdateConfig(r.Context()) + if err != nil && !trace.IsNotFound(err) { + return nil, trace.Wrap(err) + } else if err == nil { + response.ToolsAutoupdate = autoUpdateConfig.GetSpec().GetToolsAutoupdate() + } + + autoUpdateVersion, err := h.GetAccessPoint().GetAutoUpdateVersion(r.Context()) + if err != nil && !trace.IsNotFound(err) { + return nil, trace.Wrap(err) + } else if err == nil { + response.ToolsVersion = autoUpdateVersion.GetSpec().GetToolsVersion() + } + + return response, nil } func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { From 31b83162022f741bd13a127438153ad96d15982f Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Fri, 20 Sep 2024 12:56:57 -0400 Subject: [PATCH 2/3] Group auto update settings in find response Log error instead returning error Add tests auto update settings in find endpoint Add check for not implemented error --- api/client/webclient/webclient.go | 14 ++++++++--- lib/web/apiserver.go | 18 +++++++------ lib/web/apiserver_ping_test.go | 42 +++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index 249eb159c61bb..c538363b46e58 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -297,10 +297,8 @@ type PingResponse struct { ServerVersion string `json:"server_version"` // MinClientVersion is the minimum client version required by the server. MinClientVersion string `json:"min_client_version"` - // ToolsVersion defines the version of {tsh, tctl} for client auto update. - ToolsVersion string `json:"tools_version"` - // ToolsAutoupdate enables client auto update feature. - ToolsAutoupdate bool `json:"tools_autoupdate"` + // AutoUpdateSettings contains the auto update settings. + AutoUpdate AutoUpdateSettings `json:"auto_update"` // ClusterName contains the name of the Teleport cluster. ClusterName string `json:"cluster_name"` @@ -332,6 +330,14 @@ type ProxySettings struct { TLSRoutingEnabled bool `json:"tls_routing_enabled"` } +// AutoUpdateSettings contains information about the auto update requirements. +type AutoUpdateSettings struct { + // ToolsVersion defines the version of {tsh, tctl} for client auto update. + ToolsVersion string `json:"tools_version"` + // ToolsAutoupdate enables client auto update feature. + ToolsAutoupdate bool `json:"tools_autoupdate"` +} + // KubeProxySettings is kubernetes proxy settings type KubeProxySettings struct { // Enabled is true when kubernetes proxy is enabled diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 62e6527b77a2f..e74b37e3e3ca1 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1528,18 +1528,20 @@ func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Para ClusterName: h.auth.clusterName, } - autoUpdateConfig, err := h.GetAccessPoint().GetAutoUpdateConfig(r.Context()) - if err != nil && !trace.IsNotFound(err) { - return nil, trace.Wrap(err) + autoUpdateConfig, err := h.cfg.AccessPoint.GetAutoUpdateConfig(r.Context()) + // TODO(vapopov) DELETE IN v18.0.0 + if err != nil && !trace.IsNotFound(err) && !trace.IsNotImplemented(err) { + h.logger.ErrorContext(r.Context(), "failed to receive AutoUpdateConfig", "error", err) } else if err == nil { - response.ToolsAutoupdate = autoUpdateConfig.GetSpec().GetToolsAutoupdate() + response.AutoUpdate.ToolsAutoupdate = autoUpdateConfig.GetSpec().GetToolsAutoupdate() } - autoUpdateVersion, err := h.GetAccessPoint().GetAutoUpdateVersion(r.Context()) - if err != nil && !trace.IsNotFound(err) { - return nil, trace.Wrap(err) + autoUpdateVersion, err := h.cfg.AccessPoint.GetAutoUpdateVersion(r.Context()) + // TODO(vapopov) DELETE IN v18.0.0 + if err != nil && !trace.IsNotFound(err) && !trace.IsNotImplemented(err) { + h.logger.ErrorContext(r.Context(), "failed to receive AutoUpdateVersion", "error", err) } else if err == nil { - response.ToolsVersion = autoUpdateVersion.GetSpec().GetToolsVersion() + response.AutoUpdate.ToolsVersion = autoUpdateVersion.GetSpec().GetToolsVersion() } return response, nil diff --git a/lib/web/apiserver_ping_test.go b/lib/web/apiserver_ping_test.go index ea9021f074390..0fd65afa90427 100644 --- a/lib/web/apiserver_ping_test.go +++ b/lib/web/apiserver_ping_test.go @@ -29,9 +29,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/autoupdate" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/modules" ) @@ -244,5 +247,44 @@ func TestPing_minimalAPI(t *testing.T) { require.NoError(t, resp.Body.Close()) }) } +} + +// TestPing_autoUpdateResources tests that find endpoint return correct data related to auto updates. +func TestPing_autoUpdateResources(t *testing.T) { + env := newWebPack(t, 1, func(cfg *proxyConfig) { + cfg.minimalHandler = true + }) + proxy := env.proxies[0] + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Enable tools auto update. + config, err := autoupdate.NewAutoUpdateConfig(&autoupdatev1pb.AutoUpdateConfigSpec{ + ToolsAutoupdate: true, + }) + require.NoError(t, err) + _, err = env.server.Auth().UpsertAutoUpdateConfig(ctx, config) + require.NoError(t, err) + + // Set the client tools version. + version, err := autoupdate.NewAutoUpdateVersion(&autoupdatev1pb.AutoUpdateVersionSpec{ + ToolsVersion: teleport.Version, + }) + require.NoError(t, err) + _, err = env.server.Auth().UpsertAutoUpdateVersion(ctx, version) + require.NoError(t, err) + + req, err := http.NewRequest(http.MethodGet, proxy.newClient(t).Endpoint("webapi", "find"), nil) + require.NoError(t, err) + req.Host = proxy.handler.handler.cfg.ProxyPublicAddrs[0].Host() + resp, err := client.NewInsecureWebClient().Do(req) + require.NoError(t, err) + + pr := &webclient.PingResponse{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(pr)) + require.NoError(t, resp.Body.Close()) + assert.True(t, pr.AutoUpdate.ToolsAutoupdate, "tools_autoupdate must be enabled") + assert.Equal(t, teleport.Version, pr.AutoUpdate.ToolsVersion, "tools_version must be equal to current version") } From e3c3bba7bb2d82d5c0ffd4571292b9f89654c624 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Mon, 30 Sep 2024 23:49:47 -0400 Subject: [PATCH 3/3] Add more test cases --- api/client/webclient/webclient.go | 4 +- lib/web/apiserver.go | 6 +-- lib/web/apiserver_ping_test.go | 90 ++++++++++++++++++++++--------- 3 files changed, 71 insertions(+), 29 deletions(-) diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index c538363b46e58..d1f11a5a48304 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -334,8 +334,8 @@ type ProxySettings struct { type AutoUpdateSettings struct { // ToolsVersion defines the version of {tsh, tctl} for client auto update. ToolsVersion string `json:"tools_version"` - // ToolsAutoupdate enables client auto update feature. - ToolsAutoupdate bool `json:"tools_autoupdate"` + // ToolsAutoUpdate enables client auto update feature. + ToolsAutoUpdate bool `json:"tools_auto_update"` } // KubeProxySettings is kubernetes proxy settings diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index e74b37e3e3ca1..ec0172649f3b8 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1529,15 +1529,15 @@ func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Para } autoUpdateConfig, err := h.cfg.AccessPoint.GetAutoUpdateConfig(r.Context()) - // TODO(vapopov) DELETE IN v18.0.0 + // TODO(vapopov) DELETE IN v18.0.0 check of IsNotImplemented, must be backported to all latest supported versions. if err != nil && !trace.IsNotFound(err) && !trace.IsNotImplemented(err) { h.logger.ErrorContext(r.Context(), "failed to receive AutoUpdateConfig", "error", err) } else if err == nil { - response.AutoUpdate.ToolsAutoupdate = autoUpdateConfig.GetSpec().GetToolsAutoupdate() + response.AutoUpdate.ToolsAutoUpdate = autoUpdateConfig.GetSpec().GetToolsAutoupdate() } autoUpdateVersion, err := h.cfg.AccessPoint.GetAutoUpdateVersion(r.Context()) - // TODO(vapopov) DELETE IN v18.0.0 + // TODO(vapopov) DELETE IN v18.0.0 check of IsNotImplemented, must be backported to all latest supported versions. if err != nil && !trace.IsNotFound(err) && !trace.IsNotImplemented(err) { h.logger.ErrorContext(r.Context(), "failed to receive AutoUpdateVersion", "error", err) } else if err == nil { diff --git a/lib/web/apiserver_ping_test.go b/lib/web/apiserver_ping_test.go index 0fd65afa90427..5a54fd2407555 100644 --- a/lib/web/apiserver_ping_test.go +++ b/lib/web/apiserver_ping_test.go @@ -26,10 +26,10 @@ import ( "testing" "github.com/gravitational/roundtrip" + "github.com/gravitational/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" @@ -259,32 +259,74 @@ func TestPing_autoUpdateResources(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Enable tools auto update. - config, err := autoupdate.NewAutoUpdateConfig(&autoupdatev1pb.AutoUpdateConfigSpec{ - ToolsAutoupdate: true, - }) - require.NoError(t, err) - _, err = env.server.Auth().UpsertAutoUpdateConfig(ctx, config) - require.NoError(t, err) - - // Set the client tools version. - version, err := autoupdate.NewAutoUpdateVersion(&autoupdatev1pb.AutoUpdateVersionSpec{ - ToolsVersion: teleport.Version, - }) - require.NoError(t, err) - _, err = env.server.Auth().UpsertAutoUpdateVersion(ctx, version) - require.NoError(t, err) - req, err := http.NewRequest(http.MethodGet, proxy.newClient(t).Endpoint("webapi", "find"), nil) require.NoError(t, err) req.Host = proxy.handler.handler.cfg.ProxyPublicAddrs[0].Host() - resp, err := client.NewInsecureWebClient().Do(req) - require.NoError(t, err) - pr := &webclient.PingResponse{} - require.NoError(t, json.NewDecoder(resp.Body).Decode(pr)) - require.NoError(t, resp.Body.Close()) + tests := []struct { + name string + config *autoupdatev1pb.AutoUpdateConfigSpec + version *autoupdatev1pb.AutoUpdateVersionSpec + cleanup bool + expected webclient.AutoUpdateSettings + }{ + { + name: "resources not defined", + expected: webclient.AutoUpdateSettings{}, + }, + { + name: "enable auto update", + config: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + expected: webclient.AutoUpdateSettings{ToolsAutoUpdate: true}, + cleanup: true, + }, + { + name: "set auto update version", + version: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + expected: webclient.AutoUpdateSettings{ToolsVersion: "1.2.3"}, + cleanup: true, + }, + { + name: "enable auto update and set version", + config: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + version: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + expected: webclient.AutoUpdateSettings{ToolsAutoUpdate: true, ToolsVersion: "1.2.3"}, + }, + { + name: "modify auto update config and version", + config: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: false}, + version: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "3.2.1"}, + expected: webclient.AutoUpdateSettings{ToolsAutoUpdate: false, ToolsVersion: "3.2.1"}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.config != nil { + config, err := autoupdate.NewAutoUpdateConfig(tc.config) + require.NoError(t, err) + _, err = env.server.Auth().UpsertAutoUpdateConfig(ctx, config) + require.NoError(t, err) + } + if tc.version != nil { + version, err := autoupdate.NewAutoUpdateVersion(tc.version) + require.NoError(t, err) + _, err = env.server.Auth().UpsertAutoUpdateVersion(ctx, version) + require.NoError(t, err) + } - assert.True(t, pr.AutoUpdate.ToolsAutoupdate, "tools_autoupdate must be enabled") - assert.Equal(t, teleport.Version, pr.AutoUpdate.ToolsVersion, "tools_version must be equal to current version") + resp, err := client.NewInsecureWebClient().Do(req) + require.NoError(t, err) + + pr := &webclient.PingResponse{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(pr)) + require.NoError(t, resp.Body.Close()) + + assert.Equal(t, tc.expected, pr.AutoUpdate) + + if tc.cleanup { + require.NotErrorIs(t, env.server.Auth().DeleteAutoUpdateConfig(ctx), &trace.NotFoundError{}) + require.NotErrorIs(t, env.server.Auth().DeleteAutoUpdateVersion(ctx), &trace.NotFoundError{}) + } + }) + } }