Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement immediate schedule support for automatic updates #47920

Merged
merged 4 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/client/webclient/webclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ type PingResponse struct {
// reserved: license_warnings ([]string)
// AutomaticUpgrades describes whether agents should automatically upgrade.
AutomaticUpgrades bool `json:"automatic_upgrades"`
// Edition represents the Teleport edition. Possible values are "oss", "ent", and "community".
Edition string `json:"edition"`
// FIPS represents if Teleport is using FIPS-compliant cryptography.
FIPS bool `json:"fips"`
}

// PingErrorResponse contains the error from /webapi/ping.
Expand Down Expand Up @@ -336,6 +340,12 @@ type AutoUpdateSettings struct {
ToolsVersion string `json:"tools_version"`
// ToolsMode defines mode client auto update feature `enabled|disabled`.
ToolsMode string `json:"tools_mode"`
// AgentVersion defines the version of teleport that agents enrolled into autoupdates should run.
AgentVersion string `json:"agent_version"`
// AgentAutoUpdate indicates if the requesting agent should attempt to update now.
AgentAutoUpdate bool `json:"agent_auto_update"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticing that this is inconsistent with the tools_mode field that has the same meaning.

@vapopov The _mode fields were intended for configuration, not for the command sent to clients/agents. Could we change ToolsMode string to ToolsAutoUpdate bool to match this PR?

// AgentUpdateJitterSeconds defines the jitter time an agent should wait before updating.
AgentUpdateJitterSeconds int `json:"agent_update_jitter_seconds"`
}

// KubeProxySettings is kubernetes proxy settings
Expand Down
12 changes: 6 additions & 6 deletions api/types/autoupdate/rollout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestNewAutoUpdateAgentRollout(t *testing.T) {
spec: &autoupdate.AutoUpdateAgentRolloutSpec{
StartVersion: "1.2.3",
TargetVersion: "2.3.4-dev",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
AutoupdateMode: AgentsUpdateModeEnabled,
Strategy: AgentsStrategyHaltOnError,
},
Expand All @@ -57,7 +57,7 @@ func TestNewAutoUpdateAgentRollout(t *testing.T) {
Spec: &autoupdate.AutoUpdateAgentRolloutSpec{
StartVersion: "1.2.3",
TargetVersion: "2.3.4-dev",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
AutoupdateMode: AgentsUpdateModeEnabled,
Strategy: AgentsStrategyHaltOnError,
},
Expand All @@ -74,7 +74,7 @@ func TestNewAutoUpdateAgentRollout(t *testing.T) {
name: "missing start version",
spec: &autoupdate.AutoUpdateAgentRolloutSpec{
TargetVersion: "2.3.4-dev",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
AutoupdateMode: AgentsUpdateModeEnabled,
Strategy: AgentsStrategyHaltOnError,
},
Expand All @@ -87,7 +87,7 @@ func TestNewAutoUpdateAgentRollout(t *testing.T) {
spec: &autoupdate.AutoUpdateAgentRolloutSpec{
StartVersion: "1.2.3",
TargetVersion: "2-3-4",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
AutoupdateMode: AgentsUpdateModeEnabled,
Strategy: AgentsStrategyHaltOnError,
},
Expand All @@ -100,7 +100,7 @@ func TestNewAutoUpdateAgentRollout(t *testing.T) {
spec: &autoupdate.AutoUpdateAgentRolloutSpec{
StartVersion: "1.2.3",
TargetVersion: "2.3.4-dev",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
AutoupdateMode: "invalid-mode",
Strategy: AgentsStrategyHaltOnError,
},
Expand All @@ -126,7 +126,7 @@ func TestNewAutoUpdateAgentRollout(t *testing.T) {
spec: &autoupdate.AutoUpdateAgentRolloutSpec{
StartVersion: "1.2.3",
TargetVersion: "2.3.4-dev",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
AutoupdateMode: AgentsUpdateModeEnabled,
Strategy: "invalid-strategy",
},
Expand Down
4 changes: 3 additions & 1 deletion api/types/autoupdate/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ func checkToolsMode(mode string) error {

func checkScheduleName(schedule string) error {
switch schedule {
case AgentsScheduleRegular, AgentsScheduleImmediate:
case AgentsScheduleImmediate:
return nil
case AgentsScheduleRegular:
return trace.BadParameter("regular schedule is not implemented yet")
default:
return trace.BadParameter("unsupported schedule type: %q", schedule)
}
Expand Down
12 changes: 6 additions & 6 deletions api/types/autoupdate/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
Agents: &autoupdate.AutoUpdateVersionSpecAgents{
StartVersion: "1.2.3-dev.1",
TargetVersion: "1.2.3-dev.2",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
Mode: AgentsUpdateModeEnabled,
},
},
Expand All @@ -111,7 +111,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
Agents: &autoupdate.AutoUpdateVersionSpecAgents{
StartVersion: "1.2.3-dev.1",
TargetVersion: "1.2.3-dev.2",
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
Mode: AgentsUpdateModeEnabled,
},
},
Expand All @@ -124,7 +124,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
StartVersion: "",
TargetVersion: "1.2.3",
Mode: AgentsUpdateModeEnabled,
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
},
},
assertErr: func(t *testing.T, err error, a ...any) {
Expand All @@ -138,7 +138,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
StartVersion: "1.2.3-dev",
TargetVersion: "",
Mode: AgentsUpdateModeEnabled,
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
},
},
assertErr: func(t *testing.T, err error, a ...any) {
Expand All @@ -152,7 +152,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
StartVersion: "17-0-0",
TargetVersion: "1.2.3",
Mode: AgentsUpdateModeEnabled,
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
},
},
assertErr: func(t *testing.T, err error, a ...any) {
Expand All @@ -166,7 +166,7 @@ func TestNewAutoUpdateVersion(t *testing.T) {
StartVersion: "1.2.3",
TargetVersion: "17-0-0",
Mode: AgentsUpdateModeEnabled,
Schedule: AgentsScheduleRegular,
Schedule: AgentsScheduleImmediate,
},
},
assertErr: func(t *testing.T, err error, a ...any) {
Expand Down
139 changes: 97 additions & 42 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import (
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
autoupdatepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1"
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
"github.com/gravitational/teleport/api/mfa"
Expand Down Expand Up @@ -135,6 +136,8 @@ const (
// This cache is here to protect against accidental or intentional DDoS, the TTL must be low to quickly reflect
// cluster configuration changes.
findEndpointCacheTTL = 10 * time.Second
// DefaultAgentUpdateJitterSeconds is the default jitter agents should wait before updating.
DefaultAgentUpdateJitterSeconds = 60
)

// healthCheckAppServerFunc defines a function used to perform a health check
Expand Down Expand Up @@ -1539,69 +1542,65 @@ func (h *Handler) ping(w http.ResponseWriter, r *http.Request, p httprouter.Para
MinClientVersion: teleport.MinClientVersion,
ClusterName: h.auth.clusterName,
AutomaticUpgrades: pr.ServerFeatures.GetAutomaticUpgrades(),
AutoUpdate: h.automaticUpdateSettings(r.Context()),
Edition: modules.GetModules().BuildType(),
FIPS: modules.IsBoringBinary(),
}, nil
}

func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
// cache the generic answer to avoid doing work for each request
resp, err := utils.FnCacheGet[*webclient.PingResponse](r.Context(), h.findEndpointCache, "find", func(ctx context.Context) (*webclient.PingResponse, error) {
response := webclient.PingResponse{
ServerVersion: teleport.Version,
MinClientVersion: teleport.MinClientVersion,
ClusterName: h.auth.clusterName,
}

proxyConfig, err := h.cfg.ProxySettings.GetProxySettings(r.Context())
proxyConfig, err := h.cfg.ProxySettings.GetProxySettings(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
response.Proxy = *proxyConfig

authPref, err := h.cfg.AccessPoint.GetAuthPreference(r.Context())
authPref, err := h.cfg.AccessPoint.GetAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
response.Auth = webclient.AuthenticationSettings{SignatureAlgorithmSuite: authPref.GetSignatureAlgorithmSuite()}

autoUpdateConfig, err := h.cfg.AccessPoint.GetAutoUpdateConfig(r.Context())
// 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)
}
// If we can't get the AU config or tools AU are not configured, we default to "disabled".
// This ensures we fail open and don't accidentally update agents if something is going wrong.
// If we want to enable AUs by default, it would be better to create a default "autoupdate_config" resource
// than changing this logic.
if autoUpdateConfig.GetSpec().GetTools() == nil {
response.AutoUpdate.ToolsMode = autoupdate.ToolsUpdateModeDisabled
} else {
response.AutoUpdate.ToolsMode = autoUpdateConfig.GetSpec().GetTools().GetMode()
}

autoUpdateVersion, err := h.cfg.AccessPoint.GetAutoUpdateVersion(r.Context())
// 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)
}
// If we can't get the AU version or tools AU version is not specified, we default to the current proxy version.
// This ensures we always advertise a version compatible with the cluster.
if autoUpdateVersion.GetSpec().GetTools() == nil {
response.AutoUpdate.ToolsVersion = api.Version
} else {
response.AutoUpdate.ToolsVersion = autoUpdateVersion.GetSpec().GetTools().GetTargetVersion()
}

return &response, nil
return &webclient.PingResponse{
Proxy: *proxyConfig,
Auth: webclient.AuthenticationSettings{SignatureAlgorithmSuite: authPref.GetSignatureAlgorithmSuite()},
ServerVersion: teleport.Version,
MinClientVersion: teleport.MinClientVersion,
ClusterName: h.auth.clusterName,
Edition: modules.GetModules().BuildType(),
FIPS: modules.IsBoringBinary(),
AutoUpdate: h.automaticUpdateSettings(ctx),
}, nil
})
if err != nil {
return nil, trace.Wrap(err)
}

// If you need to modulate the response based on the request params (will need to do this for automatic updates)
// Do it here.
return resp, nil
}

// TODO: add the request as a parameter when we'll need to modulate the content based on the UUID and group
func (h *Handler) automaticUpdateSettings(ctx context.Context) webclient.AutoUpdateSettings {
autoUpdateConfig, err := h.cfg.AccessPoint.GetAutoUpdateConfig(ctx)
// 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(ctx, "failed to receive AutoUpdateConfig", "error", err)
}

autoUpdateVersion, err := h.cfg.AccessPoint.GetAutoUpdateVersion(ctx)
// 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(ctx, "failed to receive AutoUpdateVersion", "error", err)
}

return webclient.AutoUpdateSettings{
ToolsMode: getToolsMode(autoUpdateConfig),
ToolsVersion: getToolsVersion(autoUpdateVersion),
AgentUpdateJitterSeconds: DefaultAgentUpdateJitterSeconds,
AgentVersion: getAgentVersion(autoUpdateVersion),
AgentAutoUpdate: agentShouldUpdate(autoUpdateConfig, autoUpdateVersion),
}
}

func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
authClient := h.cfg.ProxyClient
connectorName := p.ByName("connector")
Expand Down Expand Up @@ -5154,3 +5153,59 @@ func readEtagFromAppHash(fs http.FileSystem) (string, error) {

return etag, nil
}

func getToolsMode(config *autoupdatepb.AutoUpdateConfig) string {
// If we can't get the AU config or if AUs are not configured, we default to "disabled".
// This ensures we fail open and don't accidentally update agents if something is going wrong.
// If we want to enable AUs by default, it would be better to create a default "autoupdate_config" resource
// than changing this logic.
if config.GetSpec().GetTools() == nil {
return autoupdate.ToolsUpdateModeDisabled
}
return config.GetSpec().GetTools().GetMode()
}

func getToolsVersion(version *autoupdatepb.AutoUpdateVersion) string {
// If we can't get the AU version or tools AU version is not specified, we default to the current proxy version.
// This ensures we always advertise a version compatible with the cluster.
if version.GetSpec().GetTools() == nil {
return api.Version
}
return version.GetSpec().GetTools().GetTargetVersion()
}

func getAgentVersion(version *autoupdatepb.AutoUpdateVersion) string {
// If we can't get the AU version or tools AU version is not specified, we default to the current proxy version.
// This ensures we always advertise a version compatible with the cluster.
// TODO: read the version from the autoupdate_agent_rollout when the resource is implemented
if version.GetSpec().GetAgents() == nil {
return api.Version
}

return version.GetSpec().GetAgents().GetTargetVersion()
}

func agentShouldUpdate(config *autoupdatepb.AutoUpdateConfig, version *autoupdatepb.AutoUpdateVersion) bool {
// TODO: read the data from the autoupdate_agent_rollout when the resource is implemented

// If we can't get the AU config or if AUs are not configured, we default to "disabled".
// This ensures we fail open and don't accidentally update agents if something is going wrong.
// If we want to enable AUs by default, it would be better to create a default "autoupdate_config" resource
// than changing this logic.
if config.GetSpec().GetAgents() == nil {
return false
}
if version.GetSpec().GetAgents() == nil {
return false
}
configMode := config.GetSpec().GetAgents().GetMode()
versionMode := version.GetSpec().GetAgents().GetMode()

// We update only if both version and config agent modes are "enabled"
if configMode != autoupdate.AgentsUpdateModeEnabled || versionMode != autoupdate.AgentsUpdateModeEnabled {
return false
}

scheduleName := version.GetSpec().GetAgents().GetSchedule()
return scheduleName == autoupdate.AgentsScheduleImmediate
}
Loading
Loading