diff --git a/.github/ISSUE_TEMPLATE/test-plan-docs.md b/.github/ISSUE_TEMPLATE/test-plan-docs.md index 604ca810153db..e31d0b73cfd51 100644 --- a/.github/ISSUE_TEMPLATE/test-plan-docs.md +++ b/.github/ISSUE_TEMPLATE/test-plan-docs.md @@ -36,11 +36,15 @@ to determine the rollout date. git submodule add https://github.com/gravitational/teleport content/.x ``` -## Is the docs site up to date with the new release? +## Is the docs site content up to date with the new release? - [ ] Verify that Teleport version variables are correct and reflect the upcoming release. Check `docs/config.json` for this. +- [ ] Ensure that redirects (as configured in `docs/config.json`) only exist for + the default version of the docs site, and have been removed from other + versions. + - [ ] Remove version warnings in the docs that mention a version we no longer support _except_ for the last EOL version. E.g., if we no longer support version 10, remove messages saying "You need at least version n to use this diff --git a/.github/ISSUE_TEMPLATE/webtestplan.md b/.github/ISSUE_TEMPLATE/webtestplan.md index 2f905c9f3ec68..bcdac7b26219d 100644 --- a/.github/ISSUE_TEMPLATE/webtestplan.md +++ b/.github/ISSUE_TEMPLATE/webtestplan.md @@ -175,11 +175,14 @@ All actions should require re-authn with a webauthn device. Use Discover Wizard to enroll new resources and access them: -- [ ] SSH Server (teleport service, singular EC2, SSM agent) +- [ ] SSH Server using Teleport Service - [ ] Self-Hosted PostgreSQL and Mongo -- [ ] AWS RDS (singular RDS, auto discover with ECS) - [ ] Kubernetes -- [ ] AWS EKS cluster +- [ ] Using an AWS OIDC Integration + - [ ] EC2 Auto Enrollment (SSM) + - [ ] RDS flow: single database + - [ ] RDS flow: Auto Enrollment (by VPC) + - [ ] EKS Clusters - [ ] Non-guided cards link out to correct docs #### Access Lists diff --git a/api/client/dynamicwindows/dynamicwindows.go b/api/client/dynamicwindows/dynamicwindows.go index 6c158a39e4243..32ba1762f1aed 100644 --- a/api/client/dynamicwindows/dynamicwindows.go +++ b/api/client/dynamicwindows/dynamicwindows.go @@ -46,7 +46,7 @@ func (c *Client) GetDynamicWindowsDesktop(ctx context.Context, name string) (typ return desktop, trace.Wrap(err) } -func (c *Client) ListDynamicWindowsDesktop(ctx context.Context, pageSize int, pageToken string) ([]types.DynamicWindowsDesktop, string, error) { +func (c *Client) ListDynamicWindowsDesktops(ctx context.Context, pageSize int, pageToken string) ([]types.DynamicWindowsDesktop, string, error) { resp, err := c.grpcClient.ListDynamicWindowsDesktops(ctx, &dynamicwindows.ListDynamicWindowsDesktopsRequest{ PageSize: int32(pageSize), PageToken: pageToken, diff --git a/api/types/autoupdate/config.go b/api/types/autoupdate/config.go index d61c35eccf0c2..32ae056195b64 100644 --- a/api/types/autoupdate/config.go +++ b/api/types/autoupdate/config.go @@ -1,24 +1,24 @@ /* - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +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 autoupdate import ( + "time" + "github.com/gravitational/trace" "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" @@ -26,13 +26,6 @@ import ( "github.com/gravitational/teleport/api/types" ) -const ( - // ToolsUpdateModeEnabled enables client tools automatic updates. - ToolsUpdateModeEnabled = "enabled" - // ToolsUpdateModeDisabled disables client tools automatic updates. - ToolsUpdateModeDisabled = "disabled" -) - // NewAutoUpdateConfig creates a new auto update configuration resource. func NewAutoUpdateConfig(spec *autoupdate.AutoUpdateConfigSpec) (*autoupdate.AutoUpdateConfig, error) { config := &autoupdate.AutoUpdateConfig{ @@ -66,10 +59,41 @@ func ValidateAutoUpdateConfig(c *autoupdate.AutoUpdateConfig) error { return trace.BadParameter("Spec is nil") } if c.Spec.Tools != nil { - if c.Spec.Tools.Mode != ToolsUpdateModeDisabled && c.Spec.Tools.Mode != ToolsUpdateModeEnabled { - return trace.BadParameter("ToolsMode is not valid") + if err := checkToolsMode(c.Spec.Tools.Mode); err != nil { + return trace.Wrap(err, "validating spec.tools.mode") + } + } + if c.Spec.Agents != nil { + if err := checkAgentsMode(c.Spec.Agents.Mode); err != nil { + return trace.Wrap(err, "validating spec.agents.mode") + } + if err := checkAgentsStrategy(c.Spec.Agents.Strategy); err != nil { + return trace.Wrap(err, "validating spec.agents.strategy") } + + windowDuration := c.Spec.Agents.MaintenanceWindowDuration.AsDuration() + if c.Spec.Agents.Strategy == AgentsStrategyHaltOnError && windowDuration != 0 { + return trace.BadParameter("spec.agents.maintenance_window_duration must be zero when the strategy is %q", c.Spec.Agents.Strategy) + } + if c.Spec.Agents.Strategy == AgentsStrategyTimeBased && windowDuration < 10*time.Minute { + return trace.BadParameter("spec.agents.maintenance_window_duration must be greater than 10 minutes when the strategy is %q", c.Spec.Agents.Strategy) + } + + if err := checkAgentSchedules(c.Spec.Agents.Schedules); err != nil { + return trace.Wrap(err, "validating spec.agents.schedules") + } + } return nil } + +func checkAgentSchedules(schedules *autoupdate.AgentAutoUpdateSchedules) error { + // TODO: change this logic when we implement group support. + // Currently we reject any non-nil schedule + // When we'll implement schedule support, we'll treat an empty schedule as the default schedule. + if schedules == nil { + return nil + } + return trace.NotImplemented("agent schedules are not implemented yet") +} diff --git a/api/types/autoupdate/config_test.go b/api/types/autoupdate/config_test.go index 443d6f246fa56..f6b6a87aa6bd8 100644 --- a/api/types/autoupdate/config_test.go +++ b/api/types/autoupdate/config_test.go @@ -1,29 +1,29 @@ /* - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +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 autoupdate import ( "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" @@ -99,7 +99,121 @@ func TestNewAutoUpdateConfig(t *testing.T) { }, }, assertErr: func(t *testing.T, err error, a ...any) { - require.ErrorContains(t, err, "ToolsMode is not valid") + require.ErrorContains(t, err, "unsupported tools mode: \"invalid-mode\"") + }, + }, + { + name: "invalid agents mode", + spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: "invalid-mode", + Strategy: AgentsStrategyHaltOnError, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "unsupported agents mode: \"invalid-mode\"") + }, + }, + { + name: "invalid agents strategy", + spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: "invalid-strategy", + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "unsupported agents strategy: \"invalid-strategy\"") + }, + }, + { + name: "invalid agents non-nil maintenance window with halt-on-error", + spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + MaintenanceWindowDuration: durationpb.New(time.Hour), + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "maintenance_window_duration must be zero") + }, + }, + { + name: "invalid agents nil maintenance window with time-based strategy", + spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyTimeBased, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "maintenance_window_duration must be greater than 10 minutes") + }, + }, + { + name: "invalid agents short maintenance window", + spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyTimeBased, + MaintenanceWindowDuration: durationpb.New(time.Minute), + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "maintenance_window_duration must be greater than 10 minutes") + }, + }, + { + name: "success agents autoupdate halt-on-failure", + spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.NoError(t, err) + }, + want: &autoupdate.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateConfig, + }, + Spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + }, + }, + }, + }, + { + name: "success agents autoupdate time-based", + spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyTimeBased, + MaintenanceWindowDuration: durationpb.New(time.Hour), + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.NoError(t, err) + }, + want: &autoupdate.AutoUpdateConfig{ + Kind: types.KindAutoUpdateConfig, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateConfig, + }, + Spec: &autoupdate.AutoUpdateConfigSpec{ + Agents: &autoupdate.AutoUpdateConfigSpecAgents{ + Mode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyTimeBased, + MaintenanceWindowDuration: durationpb.New(time.Hour), + }, + }, }, }, } diff --git a/api/types/autoupdate/constants.go b/api/types/autoupdate/constants.go new file mode 100644 index 0000000000000..deed5168fb21f --- /dev/null +++ b/api/types/autoupdate/constants.go @@ -0,0 +1,46 @@ +/* +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 autoupdate + +const ( + // ToolsUpdateModeEnabled enables client tools automatic updates. + ToolsUpdateModeEnabled = "enabled" + // ToolsUpdateModeDisabled disables client tools automatic updates. + ToolsUpdateModeDisabled = "disabled" + + // AgentsUpdateModeEnabled enabled agent automatic updates. + AgentsUpdateModeEnabled = "enabled" + // AgentsUpdateModeDisabled disables agent automatic updates. + AgentsUpdateModeDisabled = "disabled" + // AgentsUpdateModeSuspended temporarily suspends agent automatic updates. + AgentsUpdateModeSuspended = "suspended" + + // AgentsScheduleRegular is the regular agent update schedule. + AgentsScheduleRegular = "regular" + // AgentsScheduleImmediate is the immediate agent update schedule. + // Every agent must update immediately if it's not already running the target version. + // This can be used to recover agents in case of major incident or actively exploited vulnerability. + AgentsScheduleImmediate = "immediate" + + // AgentsStrategyHaltOnError is the agent update strategy that updates groups sequentially + // according to their order in the schedule. The previous groups must succeed. + AgentsStrategyHaltOnError = "halt-on-error" + // AgentsStrategyTimeBased is the agent update strategy that updates groups solely based on their + // maintenance window. There is no dependency between groups. Agents won't be instructed to update + // if the window is over. + AgentsStrategyTimeBased = "time-based" +) diff --git a/api/types/autoupdate/rollout.go b/api/types/autoupdate/rollout.go new file mode 100644 index 0000000000000..814d71313d3ed --- /dev/null +++ b/api/types/autoupdate/rollout.go @@ -0,0 +1,76 @@ +/* +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 autoupdate + +import ( + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/api/types" +) + +// NewAutoUpdateAgentRollout creates a new auto update version resource. +func NewAutoUpdateAgentRollout(spec *autoupdate.AutoUpdateAgentRolloutSpec) (*autoupdate.AutoUpdateAgentRollout, error) { + version := &autoupdate.AutoUpdateAgentRollout{ + Kind: types.KindAutoUpdateAgentRollout, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: spec, + } + if err := ValidateAutoUpdateAgentRollout(version); err != nil { + return nil, trace.Wrap(err) + } + + return version, nil +} + +// ValidateAutoUpdateAgentRollout checks that required parameters are set +// for the specified AutoUpdateAgentRollout. +func ValidateAutoUpdateAgentRollout(v *autoupdate.AutoUpdateAgentRollout) error { + if v == nil { + return trace.BadParameter("AutoUpdateAgentRollout is nil") + } + if v.Metadata == nil { + return trace.BadParameter("Metadata is nil") + } + if v.Metadata.Name != types.MetaNameAutoUpdateAgentRollout { + return trace.BadParameter("Name is not valid") + } + if v.Spec == nil { + return trace.BadParameter("Spec is nil") + } + if err := checkVersion(v.Spec.StartVersion); err != nil { + return trace.Wrap(err, "validating spec.start_version") + } + if err := checkVersion(v.Spec.TargetVersion); err != nil { + return trace.Wrap(err, "validating spec.target_version") + } + if err := checkAgentsMode(v.Spec.AutoupdateMode); err != nil { + return trace.Wrap(err, "validating spec.autoupdate_mode") + } + if err := checkScheduleName(v.Spec.Schedule); err != nil { + return trace.Wrap(err, "validating spec.schedule") + } + if err := checkAgentsStrategy(v.Spec.Strategy); err != nil { + return trace.Wrap(err, "validating spec.strategy") + } + + return nil +} diff --git a/api/types/autoupdate/rollout_test.go b/api/types/autoupdate/rollout_test.go new file mode 100644 index 0000000000000..cce4dc8495d83 --- /dev/null +++ b/api/types/autoupdate/rollout_test.go @@ -0,0 +1,145 @@ +/* +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 autoupdate + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/api/types" +) + +// TestNewAutoUpdateConfig verifies validation for AutoUpdateConfig resource. +func TestNewAutoUpdateAgentRollout(t *testing.T) { + tests := []struct { + name string + spec *autoupdate.AutoUpdateAgentRolloutSpec + want *autoupdate.AutoUpdateAgentRollout + assertErr func(*testing.T, error, ...any) + }{ + { + name: "success valid rollout", + spec: &autoupdate.AutoUpdateAgentRolloutSpec{ + StartVersion: "1.2.3", + TargetVersion: "2.3.4-dev", + Schedule: AgentsScheduleRegular, + AutoupdateMode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.NoError(t, err) + }, + want: &autoupdate.AutoUpdateAgentRollout{ + Kind: types.KindAutoUpdateAgentRollout, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: &autoupdate.AutoUpdateAgentRolloutSpec{ + StartVersion: "1.2.3", + TargetVersion: "2.3.4-dev", + Schedule: AgentsScheduleRegular, + AutoupdateMode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + }, + }, + }, + { + name: "missing spec", + spec: nil, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "Spec is nil") + }, + }, + { + name: "missing start version", + spec: &autoupdate.AutoUpdateAgentRolloutSpec{ + TargetVersion: "2.3.4-dev", + Schedule: AgentsScheduleRegular, + AutoupdateMode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "start_version\n\tversion is unset") + }, + }, + { + name: "invalid target version", + spec: &autoupdate.AutoUpdateAgentRolloutSpec{ + StartVersion: "1.2.3", + TargetVersion: "2-3-4", + Schedule: AgentsScheduleRegular, + AutoupdateMode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "target_version\n\tversion \"2-3-4\" is not a valid semantic version") + }, + }, + { + name: "invalid autoupdate mode", + spec: &autoupdate.AutoUpdateAgentRolloutSpec{ + StartVersion: "1.2.3", + TargetVersion: "2.3.4-dev", + Schedule: AgentsScheduleRegular, + AutoupdateMode: "invalid-mode", + Strategy: AgentsStrategyHaltOnError, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "unsupported agents mode: \"invalid-mode\"") + }, + }, + { + name: "invalid schedule name", + spec: &autoupdate.AutoUpdateAgentRolloutSpec{ + StartVersion: "1.2.3", + TargetVersion: "2.3.4-dev", + Schedule: "invalid-schedule", + AutoupdateMode: AgentsUpdateModeEnabled, + Strategy: AgentsStrategyHaltOnError, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "unsupported schedule type: \"invalid-schedule\"") + }, + }, + { + name: "invalid strategy", + spec: &autoupdate.AutoUpdateAgentRolloutSpec{ + StartVersion: "1.2.3", + TargetVersion: "2.3.4-dev", + Schedule: AgentsScheduleRegular, + AutoupdateMode: AgentsUpdateModeEnabled, + Strategy: "invalid-strategy", + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "unsupported agents strategy: \"invalid-strategy\"") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewAutoUpdateAgentRollout(tt.spec) + tt.assertErr(t, err) + require.Empty(t, cmp.Diff(got, tt.want, protocmp.Transform())) + }) + } +} diff --git a/api/types/autoupdate/utils.go b/api/types/autoupdate/utils.go new file mode 100644 index 0000000000000..4772ff8a94411 --- /dev/null +++ b/api/types/autoupdate/utils.go @@ -0,0 +1,68 @@ +/* +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 autoupdate + +import ( + "github.com/coreos/go-semver/semver" + "github.com/gravitational/trace" +) + +func checkVersion(version string) error { + if version == "" { + return trace.BadParameter("version is unset") + } + if _, err := semver.NewVersion(version); err != nil { + return trace.BadParameter("version %q is not a valid semantic version", version) + } + return nil +} + +func checkAgentsMode(mode string) error { + switch mode { + case AgentsUpdateModeEnabled, AgentsUpdateModeDisabled, AgentsUpdateModeSuspended: + return nil + default: + return trace.BadParameter("unsupported agents mode: %q", mode) + } +} + +func checkToolsMode(mode string) error { + switch mode { + case ToolsUpdateModeEnabled, ToolsUpdateModeDisabled: + return nil + default: + return trace.BadParameter("unsupported tools mode: %q", mode) + } +} + +func checkScheduleName(schedule string) error { + switch schedule { + case AgentsScheduleRegular, AgentsScheduleImmediate: + return nil + default: + return trace.BadParameter("unsupported schedule type: %q", schedule) + } +} + +func checkAgentsStrategy(strategy string) error { + switch strategy { + case AgentsStrategyHaltOnError, AgentsStrategyTimeBased: + return nil + default: + return trace.BadParameter("unsupported agents strategy: %q", strategy) + } +} diff --git a/api/types/autoupdate/version.go b/api/types/autoupdate/version.go index ad2d12f265949..4bfe14c53fc1f 100644 --- a/api/types/autoupdate/version.go +++ b/api/types/autoupdate/version.go @@ -1,25 +1,22 @@ /* - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +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 autoupdate import ( - "github.com/coreos/go-semver/semver" "github.com/gravitational/trace" "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" @@ -61,11 +58,22 @@ func ValidateAutoUpdateVersion(v *autoupdate.AutoUpdateVersion) error { } if v.Spec.Tools != nil { - if v.Spec.Tools.TargetVersion == "" { - return trace.BadParameter("TargetVersion is unset") + if err := checkVersion(v.Spec.Tools.TargetVersion); err != nil { + return trace.Wrap(err, "validating spec.tools.target_version") + } + } + if v.Spec.Agents != nil { + if err := checkVersion(v.Spec.Agents.StartVersion); err != nil { + return trace.Wrap(err, "validating spec.agents.start_version") + } + if err := checkVersion(v.Spec.Agents.TargetVersion); err != nil { + return trace.Wrap(err, "validating spec.agents.target_version") + } + if err := checkAgentsMode(v.Spec.Agents.Mode); err != nil { + return trace.Wrap(err, "validating spec.agents.mode") } - if _, err := semver.NewVersion(v.Spec.Tools.TargetVersion); err != nil { - return trace.BadParameter("TargetVersion is not a valid semantic version") + if err := checkScheduleName(v.Spec.Agents.Schedule); err != nil { + return trace.Wrap(err, "validating spec.agents.schedule") } } diff --git a/api/types/autoupdate/version_test.go b/api/types/autoupdate/version_test.go index 70790a204b219..a59a4f6fe6c22 100644 --- a/api/types/autoupdate/version_test.go +++ b/api/types/autoupdate/version_test.go @@ -1,20 +1,18 @@ /* - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +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 autoupdate @@ -69,7 +67,7 @@ func TestNewAutoUpdateVersion(t *testing.T) { }, }, assertErr: func(t *testing.T, err error, a ...any) { - require.ErrorContains(t, err, "TargetVersion is unset") + require.ErrorContains(t, err, "target_version\n\tversion is unset") }, }, { @@ -80,7 +78,7 @@ func TestNewAutoUpdateVersion(t *testing.T) { }, }, assertErr: func(t *testing.T, err error, a ...any) { - require.ErrorContains(t, err, "TargetVersion is not a valid semantic version") + require.ErrorContains(t, err, "target_version\n\tversion \"17-0-0\" is not a valid semantic version") }, }, { @@ -90,6 +88,91 @@ func TestNewAutoUpdateVersion(t *testing.T) { require.ErrorContains(t, err, "Spec is nil") }, }, + { + name: "success agents autoupdate version", + spec: &autoupdate.AutoUpdateVersionSpec{ + Agents: &autoupdate.AutoUpdateVersionSpecAgents{ + StartVersion: "1.2.3-dev.1", + TargetVersion: "1.2.3-dev.2", + Schedule: AgentsScheduleRegular, + Mode: AgentsUpdateModeEnabled, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.NoError(t, err) + }, + want: &autoupdate.AutoUpdateVersion{ + Kind: types.KindAutoUpdateVersion, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateVersion, + }, + Spec: &autoupdate.AutoUpdateVersionSpec{ + Agents: &autoupdate.AutoUpdateVersionSpecAgents{ + StartVersion: "1.2.3-dev.1", + TargetVersion: "1.2.3-dev.2", + Schedule: AgentsScheduleRegular, + Mode: AgentsUpdateModeEnabled, + }, + }, + }, + }, + { + name: "invalid empty agents start version", + spec: &autoupdate.AutoUpdateVersionSpec{ + Agents: &autoupdate.AutoUpdateVersionSpecAgents{ + StartVersion: "", + TargetVersion: "1.2.3", + Mode: AgentsUpdateModeEnabled, + Schedule: AgentsScheduleRegular, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "start_version\n\tversion is unset") + }, + }, + { + name: "invalid empty agents target version", + spec: &autoupdate.AutoUpdateVersionSpec{ + Agents: &autoupdate.AutoUpdateVersionSpecAgents{ + StartVersion: "1.2.3-dev", + TargetVersion: "", + Mode: AgentsUpdateModeEnabled, + Schedule: AgentsScheduleRegular, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "target_version\n\tversion is unset") + }, + }, + { + name: "invalid semantic agents start version", + spec: &autoupdate.AutoUpdateVersionSpec{ + Agents: &autoupdate.AutoUpdateVersionSpecAgents{ + StartVersion: "17-0-0", + TargetVersion: "1.2.3", + Mode: AgentsUpdateModeEnabled, + Schedule: AgentsScheduleRegular, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "start_version\n\tversion \"17-0-0\" is not a valid semantic version") + }, + }, + { + name: "invalid semantic agents target version", + spec: &autoupdate.AutoUpdateVersionSpec{ + Agents: &autoupdate.AutoUpdateVersionSpecAgents{ + StartVersion: "1.2.3", + TargetVersion: "17-0-0", + Mode: AgentsUpdateModeEnabled, + Schedule: AgentsScheduleRegular, + }, + }, + assertErr: func(t *testing.T, err error, a ...any) { + require.ErrorContains(t, err, "target_version\n\tversion \"17-0-0\" is not a valid semantic version") + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/api/types/common/constants.go b/api/types/common/constants.go index 0c9b541e19bbc..431393e706fe8 100644 --- a/api/types/common/constants.go +++ b/api/types/common/constants.go @@ -69,6 +69,11 @@ const ( // OriginEntraID indicates that the resource was imported // from the Entra ID directory. OriginEntraID = "entra-id" + + // OriginAWSIdentityCenter indicates that the resource was + // imported from the AWS Identity Center or created from + // the AWS Identity Center plugin. + OriginAWSIdentityCenter = "aws-identity-center" ) // OriginValues lists all possible origin values. @@ -82,4 +87,5 @@ var OriginValues = []string{ OriginSCIM, OriginDiscoveryKubernetes, OriginEntraID, + OriginAWSIdentityCenter, } diff --git a/api/types/constants.go b/api/types/constants.go index 87c0335586bf6..b50b91c480aad 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -331,12 +331,18 @@ const ( // KindAutoUpdateVersion is the resource with autoupdate versions. KindAutoUpdateVersion = "autoupdate_version" + // KindAutoUpdateAgentRollout is the resource that controls and tracks agent rollouts. + KindAutoUpdateAgentRollout = "autoupdate_agent_rollout" + // MetaNameAutoUpdateConfig is the name of a configuration resource for autoupdate config. MetaNameAutoUpdateConfig = "autoupdate-config" // MetaNameAutoUpdateVersion is the name of a resource for autoupdate version. MetaNameAutoUpdateVersion = "autoupdate-version" + // MetaNameAutoUpdateAgentRollout is the name of the autoupdate agent rollout resource. + MetaNameAutoUpdateAgentRollout = "autoupdate-agent-rollout" + // KindClusterAuditConfig is the resource that holds cluster audit configuration. KindClusterAuditConfig = "cluster_audit_config" diff --git a/api/types/plugin.go b/api/types/plugin.go index 2645c7b39f0d7..c598856960bfa 100644 --- a/api/types/plugin.go +++ b/api/types/plugin.go @@ -42,6 +42,7 @@ var AllPluginTypes = []PluginType{ PluginTypeEntraID, PluginTypeSCIM, PluginTypeDatadog, + PluginTypeAWSIdentityCenter, } const ( @@ -75,6 +76,8 @@ const ( PluginTypeSCIM = "scim" // PluginTypeDatadog indicates the Datadog Incident Management plugin PluginTypeDatadog = "datadog" + // PluginTypeAWSIdentityCenter indicates AWS Identity Center plugin + PluginTypeAWSIdentityCenter = "aws-identity-center" ) // PluginSubkind represents the type of the plugin, e.g., access request, MDM etc. diff --git a/api/types/saml_idp_service_provider.go b/api/types/saml_idp_service_provider.go index 0a274e0fec54c..f04a7434585a2 100644 --- a/api/types/saml_idp_service_provider.go +++ b/api/types/saml_idp_service_provider.go @@ -387,7 +387,7 @@ func (am *SAMLAttributeMapping) CheckAndSetDefaults() error { // preset can be either empty or one of the supported type. func (s *SAMLIdPServiceProviderV1) checkAndSetPresetDefaults(preset string) bool { switch preset { - case "", samlsp.Unspecified: + case "", samlsp.Unspecified, samlsp.AWSIdentityCenter: return true case samlsp.GCPWorkforce: if s.GetRelayState() == "" { diff --git a/api/types/saml_idp_service_provider_test.go b/api/types/saml_idp_service_provider_test.go index 1b00aeebd78da..dd70d78b9e971 100644 --- a/api/types/saml_idp_service_provider_test.go +++ b/api/types/saml_idp_service_provider_test.go @@ -196,6 +196,15 @@ func TestNewSAMLIdPServiceProvider(t *testing.T) { errAssertion: require.NoError, preset: samlsp.Unspecified, }, + { + name: "aws-identity-center preset", + entityDescriptor: "", + entityID: "IAMShowcase", + acsURL: acsURL, + expectedEntityID: "IAMShowcase", + errAssertion: require.NoError, + preset: samlsp.AWSIdentityCenter, + }, { name: "unsupported preset value", entityDescriptor: "", diff --git a/api/types/samlsp/samlsp.go b/api/types/samlsp/samlsp.go index fa552cc75c00f..1a94cc81c49d2 100644 --- a/api/types/samlsp/samlsp.go +++ b/api/types/samlsp/samlsp.go @@ -23,6 +23,9 @@ const ( // Unspecified preset type is used in the Web UI to denote a generic SAML service // provider preset. Unspecified = "unspecified" + // AWSIdentityCenter is a SAML service provider preset name for AWS + // Identity Center. + AWSIdentityCenter = "aws-identity-center" ) const ( diff --git a/assets/aws/Makefile b/assets/aws/Makefile index f78a0cdd49186..e65933213f99b 100644 --- a/assets/aws/Makefile +++ b/assets/aws/Makefile @@ -2,7 +2,7 @@ # This must be a _released_ version of Teleport, i.e. one which has binaries # available for download on https://goteleport.com/download # Unreleased versions will fail to build. -TELEPORT_VERSION ?= 16.4.3 +TELEPORT_VERSION ?= 16.4.6 # Teleport UID is the UID of a non-privileged 'teleport' user TELEPORT_UID ?= 1007 diff --git a/docs/pages/admin-guides/access-controls/guides/impersonation.mdx b/docs/pages/admin-guides/access-controls/guides/impersonation.mdx index 2896bbdfb5444..8916069032136 100644 --- a/docs/pages/admin-guides/access-controls/guides/impersonation.mdx +++ b/docs/pages/admin-guides/access-controls/guides/impersonation.mdx @@ -86,11 +86,6 @@ spec: users: ['jenkins'] roles: ['jenkins'] - # The deny section uses the identical format as the 'allow' section. - # The deny rules always override allow rules. - deny: - node_labels: - '*': '*' ``` Create the `role` resource: @@ -207,12 +202,6 @@ spec: where: > equals(impersonate_role.metadata.labels["group"], "security") && equals(impersonate_user.metadata.labels["group"], "security") - - # The deny section uses the identical format as the 'allow' section. - # The deny rules always override allow rules. - deny: - node_labels: - '*': '*' ``` Create the resources: @@ -285,12 +274,6 @@ spec: where: > contains(user.spec.traits["group"], impersonate_role.metadata.labels["group"]) && contains(user.spec.traits["group"], impersonate_user.metadata.labels["group"]) - - # The deny section uses the identical format as the 'allow' section. - # The deny rules always override allow rules. - deny: - node_labels: - '*': '*' ``` While user traits typically come from an external identity provider, we can test diff --git a/e b/e index e4f948c672214..b5db15651a8d4 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit e4f948c6722147398b9abb2fe1075456443c4dde +Subproject commit b5db15651a8d4590601e470d3e86cc3698e011b2 diff --git a/examples/aws/terraform/AMIS.md b/examples/aws/terraform/AMIS.md index 1b775003751a4..87ccac2e7b33e 100644 --- a/examples/aws/terraform/AMIS.md +++ b/examples/aws/terraform/AMIS.md @@ -6,116 +6,116 @@ This list is updated when new AMI versions are released. ### OSS ``` -# ap-northeast-1 v16.4.3 arm64 OSS: ami-03a99db2fa775900b -# ap-northeast-1 v16.4.3 x86_64 OSS: ami-068026fd823473990 -# ap-northeast-2 v16.4.3 arm64 OSS: ami-063cb5eff8ee93ae9 -# ap-northeast-2 v16.4.3 x86_64 OSS: ami-0b5b1fd8897ecd033 -# ap-northeast-3 v16.4.3 arm64 OSS: ami-0785a98abf53559f0 -# ap-northeast-3 v16.4.3 x86_64 OSS: ami-034ce21eb596f461d -# ap-south-1 v16.4.3 arm64 OSS: ami-05adbaaa445ab4f3a -# ap-south-1 v16.4.3 x86_64 OSS: ami-04c6a2b4dec6c67c8 -# ap-southeast-1 v16.4.3 arm64 OSS: ami-0858dbe717d962d28 -# ap-southeast-1 v16.4.3 x86_64 OSS: ami-09b59d463eb157e24 -# ap-southeast-2 v16.4.3 arm64 OSS: ami-06e224a773717fd38 -# ap-southeast-2 v16.4.3 x86_64 OSS: ami-022784e5407820ded -# ca-central-1 v16.4.3 arm64 OSS: ami-02c6ed3bc0c9d824e -# ca-central-1 v16.4.3 x86_64 OSS: ami-05bc6e07efc2afffd -# eu-central-1 v16.4.3 arm64 OSS: ami-0af7ec44d5b1f6c92 -# eu-central-1 v16.4.3 x86_64 OSS: ami-03fffa5ced60deb24 -# eu-north-1 v16.4.3 arm64 OSS: ami-0fbf10c3cec893e64 -# eu-north-1 v16.4.3 x86_64 OSS: ami-0dd3359279b92a8ea -# eu-west-1 v16.4.3 arm64 OSS: ami-0b43e18f381fd3ce2 -# eu-west-1 v16.4.3 x86_64 OSS: ami-04bc718116bd8d8ba -# eu-west-2 v16.4.3 arm64 OSS: ami-08b85aee5566b6d38 -# eu-west-2 v16.4.3 x86_64 OSS: ami-08f0d8bdc46fd4e96 -# eu-west-3 v16.4.3 arm64 OSS: ami-0c7c33c805040b4f5 -# eu-west-3 v16.4.3 x86_64 OSS: ami-05f3a559217d578ab -# sa-east-1 v16.4.3 arm64 OSS: ami-04cda1b5f71cf0adf -# sa-east-1 v16.4.3 x86_64 OSS: ami-05a836867a9c59a8e -# us-east-1 v16.4.3 arm64 OSS: ami-0509993561cc232d9 -# us-east-1 v16.4.3 x86_64 OSS: ami-099176631d6e3001f -# us-east-2 v16.4.3 arm64 OSS: ami-05911a676387e6359 -# us-east-2 v16.4.3 x86_64 OSS: ami-0acb2c68a8a6bd47b -# us-west-1 v16.4.3 arm64 OSS: ami-0931a3203ab46db22 -# us-west-1 v16.4.3 x86_64 OSS: ami-015c7af6995403f1c -# us-west-2 v16.4.3 arm64 OSS: ami-08fc0e0bf4374c62b -# us-west-2 v16.4.3 x86_64 OSS: ami-0ebe4df1270296286 +# ap-northeast-1 v16.4.6 arm64 OSS: ami-0307b5460949a2d09 +# ap-northeast-1 v16.4.6 x86_64 OSS: ami-0dfab44a54daa6774 +# ap-northeast-2 v16.4.6 arm64 OSS: ami-0e55fd0db3d73dac8 +# ap-northeast-2 v16.4.6 x86_64 OSS: ami-0b8383b49bc5da0a3 +# ap-northeast-3 v16.4.6 arm64 OSS: ami-028c94128392137d3 +# ap-northeast-3 v16.4.6 x86_64 OSS: ami-0d125ae89b4371a18 +# ap-south-1 v16.4.6 arm64 OSS: ami-044c55ce57c90a643 +# ap-south-1 v16.4.6 x86_64 OSS: ami-0abbfcf08a8e28c0e +# ap-southeast-1 v16.4.6 arm64 OSS: ami-0e49484dca4bb8fb4 +# ap-southeast-1 v16.4.6 x86_64 OSS: ami-0d23cc1ba5cc1ebea +# ap-southeast-2 v16.4.6 arm64 OSS: ami-07ebd854df12f216a +# ap-southeast-2 v16.4.6 x86_64 OSS: ami-0c22e4641198f8bec +# ca-central-1 v16.4.6 arm64 OSS: ami-0c143a08dcf5ca3fd +# ca-central-1 v16.4.6 x86_64 OSS: ami-0c16191afc3fb654a +# eu-central-1 v16.4.6 arm64 OSS: ami-0551d4f5aeede709e +# eu-central-1 v16.4.6 x86_64 OSS: ami-0cd3c4404ebac1b6d +# eu-north-1 v16.4.6 arm64 OSS: ami-001a29d66d3f99d0c +# eu-north-1 v16.4.6 x86_64 OSS: ami-099742a52e0243c12 +# eu-west-1 v16.4.6 arm64 OSS: ami-0f056146565210169 +# eu-west-1 v16.4.6 x86_64 OSS: ami-0bb1098e9061f3286 +# eu-west-2 v16.4.6 arm64 OSS: ami-0c41c8a9e6623cbeb +# eu-west-2 v16.4.6 x86_64 OSS: ami-0ef2785710a394477 +# eu-west-3 v16.4.6 arm64 OSS: ami-01f68dd1652a01299 +# eu-west-3 v16.4.6 x86_64 OSS: ami-062cc20058012a2ac +# sa-east-1 v16.4.6 arm64 OSS: ami-0448cf45899c4109b +# sa-east-1 v16.4.6 x86_64 OSS: ami-00153f4b099207ba6 +# us-east-1 v16.4.6 arm64 OSS: ami-023856580215a2b43 +# us-east-1 v16.4.6 x86_64 OSS: ami-06cc987e0952a0378 +# us-east-2 v16.4.6 arm64 OSS: ami-0291a0a11bdd06c8d +# us-east-2 v16.4.6 x86_64 OSS: ami-05435267916884c4e +# us-west-1 v16.4.6 arm64 OSS: ami-06cf14c2b9bfa2fc3 +# us-west-1 v16.4.6 x86_64 OSS: ami-0646577553c25d2fb +# us-west-2 v16.4.6 arm64 OSS: ami-04b773c97239cdc04 +# us-west-2 v16.4.6 x86_64 OSS: ami-015f89025eb9ab2dd ``` ### Enterprise ``` -# ap-northeast-1 v16.4.3 arm64 Enterprise: ami-0587524cb092763b8 -# ap-northeast-1 v16.4.3 x86_64 Enterprise: ami-03d76c92521d9b178 -# ap-northeast-2 v16.4.3 arm64 Enterprise: ami-087a2c13eb63f133a -# ap-northeast-2 v16.4.3 x86_64 Enterprise: ami-030048fc4753d7456 -# ap-northeast-3 v16.4.3 arm64 Enterprise: ami-0d88ac92fc0f7c1f1 -# ap-northeast-3 v16.4.3 x86_64 Enterprise: ami-02edeee2926921c43 -# ap-south-1 v16.4.3 arm64 Enterprise: ami-0fa8621c5aac4fdd1 -# ap-south-1 v16.4.3 x86_64 Enterprise: ami-09d7e5e25b07aff41 -# ap-southeast-1 v16.4.3 arm64 Enterprise: ami-07a6b4a9e00672cfa -# ap-southeast-1 v16.4.3 x86_64 Enterprise: ami-0cad3b30fd7786746 -# ap-southeast-2 v16.4.3 arm64 Enterprise: ami-0ee80dba1cfc25ddb -# ap-southeast-2 v16.4.3 x86_64 Enterprise: ami-015871b65daf94360 -# ca-central-1 v16.4.3 arm64 Enterprise: ami-0096116bef0c781eb -# ca-central-1 v16.4.3 x86_64 Enterprise: ami-0d4217deec2fa79af -# eu-central-1 v16.4.3 arm64 Enterprise: ami-03d1da8c34a30b131 -# eu-central-1 v16.4.3 x86_64 Enterprise: ami-018d51199ce555b06 -# eu-north-1 v16.4.3 arm64 Enterprise: ami-0186b55009355474e -# eu-north-1 v16.4.3 x86_64 Enterprise: ami-009e80af1651b105a -# eu-west-1 v16.4.3 arm64 Enterprise: ami-035fab6a83d3de6b4 -# eu-west-1 v16.4.3 x86_64 Enterprise: ami-0404e446cd95923d5 -# eu-west-2 v16.4.3 arm64 Enterprise: ami-086099148dcbca910 -# eu-west-2 v16.4.3 x86_64 Enterprise: ami-0cfedd8ec032b41a9 -# eu-west-3 v16.4.3 arm64 Enterprise: ami-0a05a917e47722280 -# eu-west-3 v16.4.3 x86_64 Enterprise: ami-0d670841a9b05347f -# sa-east-1 v16.4.3 arm64 Enterprise: ami-00b649f81437accf3 -# sa-east-1 v16.4.3 x86_64 Enterprise: ami-04a90f47e22ec6b94 -# us-east-1 v16.4.3 arm64 Enterprise: ami-0964d07c4f1970766 -# us-east-1 v16.4.3 x86_64 Enterprise: ami-02a8e3fa00529a543 -# us-east-2 v16.4.3 arm64 Enterprise: ami-0f29f06b24b96c7fb -# us-east-2 v16.4.3 x86_64 Enterprise: ami-012ff8f25038b5e68 -# us-west-1 v16.4.3 arm64 Enterprise: ami-02638c230b9ddeb0f -# us-west-1 v16.4.3 x86_64 Enterprise: ami-0082e714b7e481911 -# us-west-2 v16.4.3 arm64 Enterprise: ami-04fb6f48cbe7ea880 -# us-west-2 v16.4.3 x86_64 Enterprise: ami-01af4cd28ddaaca88 +# ap-northeast-1 v16.4.6 arm64 Enterprise: ami-05f137e5b9201f996 +# ap-northeast-1 v16.4.6 x86_64 Enterprise: ami-03212c4ccbf6eca85 +# ap-northeast-2 v16.4.6 arm64 Enterprise: ami-0924720d520fe8a11 +# ap-northeast-2 v16.4.6 x86_64 Enterprise: ami-0fb3f151c13f316f2 +# ap-northeast-3 v16.4.6 arm64 Enterprise: ami-04d7f1e045968880c +# ap-northeast-3 v16.4.6 x86_64 Enterprise: ami-0a2f75caf6781d4f8 +# ap-south-1 v16.4.6 arm64 Enterprise: ami-0f9d46726c638e2d4 +# ap-south-1 v16.4.6 x86_64 Enterprise: ami-072c1bda7163cd497 +# ap-southeast-1 v16.4.6 arm64 Enterprise: ami-011f33a197bfe7e44 +# ap-southeast-1 v16.4.6 x86_64 Enterprise: ami-0fbc3bdaa3047315e +# ap-southeast-2 v16.4.6 arm64 Enterprise: ami-058180b9c6d8cc471 +# ap-southeast-2 v16.4.6 x86_64 Enterprise: ami-0aa1b5b87840a1156 +# ca-central-1 v16.4.6 arm64 Enterprise: ami-05aadbead2b91af95 +# ca-central-1 v16.4.6 x86_64 Enterprise: ami-0d08f955e2eb4cce2 +# eu-central-1 v16.4.6 arm64 Enterprise: ami-068077fc8087e409c +# eu-central-1 v16.4.6 x86_64 Enterprise: ami-0ec63c9cf4ec58ef1 +# eu-north-1 v16.4.6 arm64 Enterprise: ami-04116b37191302aec +# eu-north-1 v16.4.6 x86_64 Enterprise: ami-0ff60c42661fd7ea6 +# eu-west-1 v16.4.6 arm64 Enterprise: ami-0b8f5a17eeecef1a2 +# eu-west-1 v16.4.6 x86_64 Enterprise: ami-05f6c12ade128ff2f +# eu-west-2 v16.4.6 arm64 Enterprise: ami-0d23fa319e42572c1 +# eu-west-2 v16.4.6 x86_64 Enterprise: ami-0e6fb58e541932cf6 +# eu-west-3 v16.4.6 arm64 Enterprise: ami-0567bafa2392fe6da +# eu-west-3 v16.4.6 x86_64 Enterprise: ami-01e39ed7e04cc3dd6 +# sa-east-1 v16.4.6 arm64 Enterprise: ami-08ba21a1168c24241 +# sa-east-1 v16.4.6 x86_64 Enterprise: ami-0b23038d8ed82c2a3 +# us-east-1 v16.4.6 arm64 Enterprise: ami-0e7d540c0ed15a0cc +# us-east-1 v16.4.6 x86_64 Enterprise: ami-050d0ef0234295033 +# us-east-2 v16.4.6 arm64 Enterprise: ami-0c94bb5cd6d326f4a +# us-east-2 v16.4.6 x86_64 Enterprise: ami-04fa696ef2b274943 +# us-west-1 v16.4.6 arm64 Enterprise: ami-0b43f032b82b354f0 +# us-west-1 v16.4.6 x86_64 Enterprise: ami-0f927c0e8705a2aaa +# us-west-2 v16.4.6 arm64 Enterprise: ami-0379aa56700286ccd +# us-west-2 v16.4.6 x86_64 Enterprise: ami-0606b81e192fa074d ``` ### Enterprise FIPS ``` -# ap-northeast-1 v16.4.3 arm64 Enterprise FIPS: ami-019cf60b62d5a10d7 -# ap-northeast-1 v16.4.3 x86_64 Enterprise FIPS: ami-059f7cfa3b78b78bb -# ap-northeast-2 v16.4.3 arm64 Enterprise FIPS: ami-0686e3978afbe194e -# ap-northeast-2 v16.4.3 x86_64 Enterprise FIPS: ami-09c75e412314e2285 -# ap-northeast-3 v16.4.3 arm64 Enterprise FIPS: ami-0cd8f2c41cf6ab3a6 -# ap-northeast-3 v16.4.3 x86_64 Enterprise FIPS: ami-0d7e615231cb9a07a -# ap-south-1 v16.4.3 arm64 Enterprise FIPS: ami-052410bcc79298775 -# ap-south-1 v16.4.3 x86_64 Enterprise FIPS: ami-0c34957c4e88fc5cb -# ap-southeast-1 v16.4.3 arm64 Enterprise FIPS: ami-009a131295b547ba4 -# ap-southeast-1 v16.4.3 x86_64 Enterprise FIPS: ami-0ae06abb3a918f1b0 -# ap-southeast-2 v16.4.3 arm64 Enterprise FIPS: ami-08c3056bb1b60a845 -# ap-southeast-2 v16.4.3 x86_64 Enterprise FIPS: ami-0406318e9822c396c -# ca-central-1 v16.4.3 arm64 Enterprise FIPS: ami-0a3156111e029549a -# ca-central-1 v16.4.3 x86_64 Enterprise FIPS: ami-0d3c0c6ed2542c23f -# eu-central-1 v16.4.3 arm64 Enterprise FIPS: ami-0f687501df4213855 -# eu-central-1 v16.4.3 x86_64 Enterprise FIPS: ami-065c83aee094e9d84 -# eu-north-1 v16.4.3 arm64 Enterprise FIPS: ami-08ca656487c08696d -# eu-north-1 v16.4.3 x86_64 Enterprise FIPS: ami-0e0458bbd101b2afd -# eu-west-1 v16.4.3 arm64 Enterprise FIPS: ami-038a541f0f3bb079b -# eu-west-1 v16.4.3 x86_64 Enterprise FIPS: ami-0e3895fc4988402eb -# eu-west-2 v16.4.3 arm64 Enterprise FIPS: ami-0d37fd8143a442afb -# eu-west-2 v16.4.3 x86_64 Enterprise FIPS: ami-03e00869abccc5778 -# eu-west-3 v16.4.3 arm64 Enterprise FIPS: ami-07454b7d4f108d56b -# eu-west-3 v16.4.3 x86_64 Enterprise FIPS: ami-0eafe00629a7e0ef0 -# sa-east-1 v16.4.3 arm64 Enterprise FIPS: ami-067f2d87167d38e61 -# sa-east-1 v16.4.3 x86_64 Enterprise FIPS: ami-0f5e7d1cae7105731 -# us-east-1 v16.4.3 arm64 Enterprise FIPS: ami-09af1d781d706b43c -# us-east-1 v16.4.3 x86_64 Enterprise FIPS: ami-0beb13e821cf7c941 -# us-east-2 v16.4.3 arm64 Enterprise FIPS: ami-016941078434174d1 -# us-east-2 v16.4.3 x86_64 Enterprise FIPS: ami-0a49d29be30d77240 -# us-west-1 v16.4.3 arm64 Enterprise FIPS: ami-00e936b6a86e30520 -# us-west-1 v16.4.3 x86_64 Enterprise FIPS: ami-0ae4df0bf4ea14c19 -# us-west-2 v16.4.3 arm64 Enterprise FIPS: ami-005e1ffa94a45bf74 -# us-west-2 v16.4.3 x86_64 Enterprise FIPS: ami-0b1e72b0e4fe47181 +# ap-northeast-1 v16.4.6 arm64 Enterprise FIPS: ami-03222719a3da72577 +# ap-northeast-1 v16.4.6 x86_64 Enterprise FIPS: ami-03b67fc78f15b9cb1 +# ap-northeast-2 v16.4.6 arm64 Enterprise FIPS: ami-07b1142c8d0a97142 +# ap-northeast-2 v16.4.6 x86_64 Enterprise FIPS: ami-0f5200769c8d1d528 +# ap-northeast-3 v16.4.6 arm64 Enterprise FIPS: ami-0c735142ffa7fe311 +# ap-northeast-3 v16.4.6 x86_64 Enterprise FIPS: ami-01f76396df08b2ea4 +# ap-south-1 v16.4.6 arm64 Enterprise FIPS: ami-0b151dab5123a1cde +# ap-south-1 v16.4.6 x86_64 Enterprise FIPS: ami-0a548663132b1fbcc +# ap-southeast-1 v16.4.6 arm64 Enterprise FIPS: ami-0ee14bd0e530b9802 +# ap-southeast-1 v16.4.6 x86_64 Enterprise FIPS: ami-02605938b8431df73 +# ap-southeast-2 v16.4.6 arm64 Enterprise FIPS: ami-0e1ab9036f0d741ee +# ap-southeast-2 v16.4.6 x86_64 Enterprise FIPS: ami-0da24aa1dca222d28 +# ca-central-1 v16.4.6 arm64 Enterprise FIPS: ami-0168f83e25bab435d +# ca-central-1 v16.4.6 x86_64 Enterprise FIPS: ami-0dd0f6a87b55bf122 +# eu-central-1 v16.4.6 arm64 Enterprise FIPS: ami-0be0fd9618583374b +# eu-central-1 v16.4.6 x86_64 Enterprise FIPS: ami-0c87fd696f03d07be +# eu-north-1 v16.4.6 arm64 Enterprise FIPS: ami-0165c744dd34b0367 +# eu-north-1 v16.4.6 x86_64 Enterprise FIPS: ami-0fc4ea5c96fe4b2a1 +# eu-west-1 v16.4.6 arm64 Enterprise FIPS: ami-064921542d826b418 +# eu-west-1 v16.4.6 x86_64 Enterprise FIPS: ami-03291e366b650c3bb +# eu-west-2 v16.4.6 arm64 Enterprise FIPS: ami-0913a3fa04526977b +# eu-west-2 v16.4.6 x86_64 Enterprise FIPS: ami-0f303317a7395c1da +# eu-west-3 v16.4.6 arm64 Enterprise FIPS: ami-0403b9e814a93e163 +# eu-west-3 v16.4.6 x86_64 Enterprise FIPS: ami-0550f4d59db15f7d7 +# sa-east-1 v16.4.6 arm64 Enterprise FIPS: ami-04479f0cc8407de74 +# sa-east-1 v16.4.6 x86_64 Enterprise FIPS: ami-0ae361c5388c2e620 +# us-east-1 v16.4.6 arm64 Enterprise FIPS: ami-0d6a7504bf4c12471 +# us-east-1 v16.4.6 x86_64 Enterprise FIPS: ami-0184537bbd4a91c80 +# us-east-2 v16.4.6 arm64 Enterprise FIPS: ami-0ba3bdc20ee3911eb +# us-east-2 v16.4.6 x86_64 Enterprise FIPS: ami-0deebe84e68d2ca33 +# us-west-1 v16.4.6 arm64 Enterprise FIPS: ami-04f75a751ff454fe0 +# us-west-1 v16.4.6 x86_64 Enterprise FIPS: ami-0dc241b1b82dcb44b +# us-west-2 v16.4.6 arm64 Enterprise FIPS: ami-0bcc7e8ade2f96862 +# us-west-2 v16.4.6 x86_64 Enterprise FIPS: ami-0e396f7cd99e781ae ``` diff --git a/examples/aws/terraform/ha-autoscale-cluster/README.md b/examples/aws/terraform/ha-autoscale-cluster/README.md index eceed030de64f..fff741d817e46 100644 --- a/examples/aws/terraform/ha-autoscale-cluster/README.md +++ b/examples/aws/terraform/ha-autoscale-cluster/README.md @@ -46,7 +46,7 @@ export TF_VAR_cluster_name="teleport.example.com" # OSS: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-oss-*' # Enterprise: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-ent-*' # FIPS 140-2 images are also available for Enterprise customers, look for '-fips' on the end of the AMI's name -export TF_VAR_ami_name="teleport-ent-16.4.3-arm64" +export TF_VAR_ami_name="teleport-ent-16.4.6-arm64" # Instance types used for authentication server auto scaling group # This should match to the AMI instance architecture type, ARM or x86 diff --git a/examples/aws/terraform/starter-cluster/README.md b/examples/aws/terraform/starter-cluster/README.md index f8ca9b8a6f1dc..5a7030487d616 100644 --- a/examples/aws/terraform/starter-cluster/README.md +++ b/examples/aws/terraform/starter-cluster/README.md @@ -98,7 +98,7 @@ TF_VAR_license_path ?= "/path/to/license" # OSS: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-oss-*' # Enterprise: aws ec2 describe-images --owners 146628656107 --filters 'Name=name,Values=teleport-ent-*' # FIPS 140-2 images are also available for Enterprise customers, look for '-fips' on the end of the AMI's name -TF_VAR_ami_name ?= "teleport-ent-16.4.3-arm64" +TF_VAR_ami_name ?= "teleport-ent-16.4.6-arm64" # Route 53 hosted zone to use, must be a root zone registered in AWS, e.g. example.com TF_VAR_route53_zone ?= "example.com" diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go index 005a44cdda8a1..4d3bdabd846b7 100644 --- a/lib/auth/authclient/clt.go +++ b/lib/auth/authclient/clt.go @@ -33,6 +33,7 @@ import ( "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/crownjewel" "github.com/gravitational/teleport/api/client/databaseobject" + "github.com/gravitational/teleport/api/client/dynamicwindows" "github.com/gravitational/teleport/api/client/externalauditstorage" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/client/secreport" @@ -1600,6 +1601,8 @@ type ClientI interface { types.WebSessionsGetter types.WebTokensGetter + DynamicDesktopClient() *dynamicwindows.Client + // TrustClient returns a client to the Trust service. TrustClient() trustpb.TrustServiceClient diff --git a/lib/srv/desktop/discovery.go b/lib/srv/desktop/discovery.go index cc7dd71daa6d2..c44bcc0a9cabb 100644 --- a/lib/srv/desktop/discovery.go +++ b/lib/srv/desktop/discovery.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "log/slog" + "maps" "net" "net/netip" "strings" @@ -32,6 +33,7 @@ import ( "github.com/go-ldap/ldap/v3" "github.com/gravitational/trace" + "github.com/gravitational/teleport" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/windows" @@ -306,3 +308,91 @@ func (s *WindowsService) ldapEntryToWindowsDesktop(ctx context.Context, entry *l desktop.SetExpiry(s.cfg.Clock.Now().UTC().Add(apidefaults.ServerAnnounceTTL * 3)) return desktop, nil } + +// startDynamicReconciler starts resource watcher and reconciler that registers/unregisters Windows desktops +// according to the up-to-date list of dynamic Windows desktops resources. +func (s *WindowsService) startDynamicReconciler(ctx context.Context) (*services.DynamicWindowsDesktopWatcher, error) { + if len(s.cfg.ResourceMatchers) == 0 { + s.cfg.Logger.DebugContext(ctx, "Not starting dynamic desktop resource watcher.") + return nil, nil + } + s.cfg.Logger.DebugContext(ctx, "Starting dynamic desktop resource watcher.") + dynamicDesktopClient := s.cfg.AuthClient.DynamicDesktopClient() + watcher, err := services.NewDynamicWindowsDesktopWatcher(ctx, services.DynamicWindowsDesktopWatcherConfig{ + DynamicWindowsDesktopGetter: dynamicDesktopClient, + ResourceWatcherConfig: services.ResourceWatcherConfig{ + Component: teleport.ComponentWindowsDesktop, + Client: s.cfg.AccessPoint, + }, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + currentResources := make(map[string]types.WindowsDesktop) + var newResources map[string]types.WindowsDesktop + + reconciler, err := services.NewReconciler(services.ReconcilerConfig[types.WindowsDesktop]{ + Matcher: func(desktop types.WindowsDesktop) bool { + return services.MatchResourceLabels(s.cfg.ResourceMatchers, desktop.GetAllLabels()) + }, + GetCurrentResources: func() map[string]types.WindowsDesktop { + return currentResources + }, + GetNewResources: func() map[string]types.WindowsDesktop { + return newResources + }, + OnCreate: s.upsertDesktop, + OnUpdate: s.updateDesktop, + OnDelete: s.deleteDesktop, + }) + if err != nil { + return nil, trace.Wrap(err) + } + go func() { + defer s.cfg.Logger.DebugContext(ctx, "DynamicWindowsDesktop resource watcher done.") + defer watcher.Close() + for { + select { + case desktops := <-watcher.DynamicWindowsDesktopsC: + newResources = make(map[string]types.WindowsDesktop) + for _, dynamicDesktop := range desktops { + desktop, err := s.toWindowsDesktop(dynamicDesktop) + if err != nil { + s.cfg.Logger.WarnContext(ctx, "Can't create desktop resource", "error", err) + continue + } + newResources[dynamicDesktop.GetName()] = desktop + } + if err := reconciler.Reconcile(ctx); err != nil { + s.cfg.Logger.WarnContext(ctx, "Reconciliation failed, will retry", "error", err) + continue + } + currentResources = newResources + case <-watcher.Done(): + return + case <-ctx.Done(): + return + } + } + }() + return watcher, nil +} + +func (s *WindowsService) toWindowsDesktop(dynamicDesktop types.DynamicWindowsDesktop) (*types.WindowsDesktopV3, error) { + width, height := dynamicDesktop.GetScreenSize() + desktopLabels := dynamicDesktop.GetAllLabels() + labels := make(map[string]string, len(desktopLabels)+1) + maps.Copy(labels, desktopLabels) + labels[types.OriginLabel] = types.OriginDynamic + return types.NewWindowsDesktopV3(dynamicDesktop.GetName(), labels, types.WindowsDesktopSpecV3{ + Addr: dynamicDesktop.GetAddr(), + Domain: dynamicDesktop.GetDomain(), + HostID: s.cfg.Heartbeat.HostUUID, + NonAD: dynamicDesktop.NonAD(), + ScreenSize: &types.Resolution{ + Width: width, + Height: height, + }, + }) +} diff --git a/lib/srv/desktop/discovery_test.go b/lib/srv/desktop/discovery_test.go index 2a35918d264b4..fc188f75ce1d6 100644 --- a/lib/srv/desktop/discovery_test.go +++ b/lib/srv/desktop/discovery_test.go @@ -33,7 +33,9 @@ import ( "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/windows" + "github.com/gravitational/teleport/lib/services" logutils "github.com/gravitational/teleport/lib/utils/log" ) @@ -169,3 +171,134 @@ func TestDNSErrors(t *testing.T) { require.Less(t, time.Since(start), dnsQueryTimeout-1*time.Second) require.Error(t, err) } + +func TestDynamicWindowsDiscovery(t *testing.T) { + t.Parallel() + authServer, err := auth.NewTestAuthServer(auth.TestAuthServerConfig{ + ClusterName: "test", + Dir: t.TempDir(), + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, authServer.Close()) + }) + + tlsServer, err := authServer.NewTestTLSServer() + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, tlsServer.Close()) + }) + + client, err := tlsServer.NewClient(auth.TestServerID(types.RoleWindowsDesktop, "test-host-id")) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, client.Close()) + }) + + dynamicWindowsClient := client.DynamicDesktopClient() + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + for _, testCase := range []struct { + name string + labels map[string]string + expected int + }{ + { + name: "no labels", + expected: 0, + }, + { + name: "no matching labels", + labels: map[string]string{"xyz": "abc"}, + expected: 0, + }, + { + name: "matching labels", + labels: map[string]string{"foo": "bar"}, + expected: 1, + }, + { + name: "matching wildcard labels", + labels: map[string]string{"abc": "abc"}, + expected: 1, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + s := &WindowsService{ + cfg: WindowsServiceConfig{ + Heartbeat: HeartbeatConfig{ + HostUUID: "1234", + }, + Logger: slog.New(logutils.NewSlogTextHandler(io.Discard, logutils.SlogTextHandlerConfig{})), + Clock: clockwork.NewFakeClock(), + AuthClient: client, + AccessPoint: client, + ResourceMatchers: []services.ResourceMatcher{{ + Labels: types.Labels{ + "foo": {"bar"}, + }, + }, { + Labels: types.Labels{ + "abc": {"*"}, + }, + }}, + }, + dnsResolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + return nil, errors.New("this resolver always fails") + }, + }, + } + reconciler, err := s.startDynamicReconciler(ctx) + require.NoError(t, err) + t.Cleanup(func() { + reconciler.Close() + require.NoError(t, authServer.AuthServer.DeleteAllWindowsDesktops(ctx)) + require.NoError(t, authServer.AuthServer.DeleteAllDynamicWindowsDesktops(ctx)) + }) + + desktop, err := types.NewDynamicWindowsDesktopV1("test", testCase.labels, types.DynamicWindowsDesktopSpecV1{ + Addr: "addr", + }) + require.NoError(t, err) + + _, err = dynamicWindowsClient.CreateDynamicWindowsDesktop(ctx, desktop) + require.NoError(t, err) + + time.Sleep(10 * time.Millisecond) + + desktops, err := client.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.Len(t, desktops, testCase.expected) + if testCase.expected > 0 { + require.Equal(t, desktop.GetName(), desktops[0].GetName()) + require.Equal(t, desktop.GetAddr(), desktops[0].GetAddr()) + } + + desktop.Spec.Addr = "addr2" + _, err = dynamicWindowsClient.UpdateDynamicWindowsDesktop(ctx, desktop) + require.NoError(t, err) + + time.Sleep(10 * time.Millisecond) + desktops, err = client.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.Len(t, desktops, testCase.expected) + if testCase.expected > 0 { + require.Equal(t, desktop.GetName(), desktops[0].GetName()) + require.Equal(t, desktop.GetAddr(), desktops[0].GetAddr()) + } + + require.NoError(t, dynamicWindowsClient.DeleteDynamicWindowsDesktop(ctx, "test")) + + time.Sleep(10 * time.Millisecond) + + desktops, err = client.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) + require.NoError(t, err) + require.Empty(t, desktops) + }) + + } +} diff --git a/lib/srv/desktop/windows_server.go b/lib/srv/desktop/windows_server.go index 28aaee2a48483..791f477861666 100644 --- a/lib/srv/desktop/windows_server.go +++ b/lib/srv/desktop/windows_server.go @@ -411,6 +411,10 @@ func NewWindowsService(cfg WindowsServiceConfig) (*WindowsService, error) { return nil, trace.Wrap(err) } + if _, err := s.startDynamicReconciler(ctx); err != nil { + return nil, trace.Wrap(err) + } + if len(s.cfg.DiscoveryBaseDN) > 0 { if err := s.startDesktopDiscovery(); err != nil { return nil, trace.Wrap(err) diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index 5719344fe4bd3..1afe9f0c7bca9 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -866,6 +866,35 @@ func (c *windowsDesktopCollection) writeJSON(w io.Writer) error { return utils.WriteJSONArray(w, c.desktops) } +type dynamicWindowsDesktopCollection struct { + desktops []types.DynamicWindowsDesktop +} + +func (c *dynamicWindowsDesktopCollection) resources() (r []types.Resource) { + r = make([]types.Resource, 0, len(c.desktops)) + for _, resource := range c.desktops { + r = append(r, resource) + } + return r +} + +func (c *dynamicWindowsDesktopCollection) writeText(w io.Writer, verbose bool) error { + var rows [][]string + for _, d := range c.desktops { + labels := common.FormatLabels(d.GetAllLabels(), verbose) + rows = append(rows, []string{d.GetName(), d.GetAddr(), d.GetDomain(), labels}) + } + headers := []string{"Name", "Address", "AD Domain", "Labels"} + var t asciitable.Table + if verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") + } + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} + type tokenCollection struct { tokens []types.ProvisionToken } diff --git a/tool/tctl/common/edit_command_test.go b/tool/tctl/common/edit_command_test.go index 9ce3a89d7bf85..c0ffbf342a485 100644 --- a/tool/tctl/common/edit_command_test.go +++ b/tool/tctl/common/edit_command_test.go @@ -91,6 +91,10 @@ func TestEditResources(t *testing.T) { kind: types.KindAutoUpdateVersion, edit: testEditAutoUpdateVersion, }, + { + kind: types.KindDynamicWindowsDesktop, + edit: testEditDynamicWindowsDesktop, + }, } for _, test := range tests { @@ -635,3 +639,35 @@ func testEditAutoUpdateVersion(t *testing.T, clt *authclient.Client) { "tools_autoupdate should have been modified by edit") assert.Equal(t, expected.GetSpec().GetTools().GetTargetVersion(), actual.GetSpec().GetTools().GetTargetVersion()) } + +func testEditDynamicWindowsDesktop(t *testing.T, clt *authclient.Client) { + ctx := context.Background() + + expected, err := types.NewDynamicWindowsDesktopV1("test", nil, types.DynamicWindowsDesktopSpecV1{ + Addr: "test", + }) + require.NoError(t, err) + created, err := clt.DynamicDesktopClient().CreateDynamicWindowsDesktop(ctx, expected) + require.NoError(t, err) + + editor := func(name string) error { + f, err := os.Create(name) + if err != nil { + return trace.Wrap(err, "opening file to edit") + } + + expected.SetRevision(created.GetRevision()) + expected.Spec.Addr = "test2" + + collection := &dynamicWindowsDesktopCollection{desktops: []types.DynamicWindowsDesktop{expected}} + return trace.NewAggregate(writeYAML(collection, f), f.Close()) + } + + _, err = runEditCommand(t, clt, []string{"edit", "dynamic_windows_desktop/test"}, withEditor(editor)) + require.NoError(t, err) + + actual, err := clt.DynamicDesktopClient().GetDynamicWindowsDesktop(ctx, expected.GetName()) + require.NoError(t, err) + expected.SetRevision(actual.GetRevision()) + require.Empty(t, cmp.Diff(expected, actual, protocmp.Transform())) +} diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 7975a80a7298f..c37e8805581e6 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -156,6 +156,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec types.KindOktaImportRule: rc.createOktaImportRule, types.KindIntegration: rc.createIntegration, types.KindWindowsDesktop: rc.createWindowsDesktop, + types.KindDynamicWindowsDesktop: rc.createDynamicWindowsDesktop, types.KindAccessList: rc.createAccessList, types.KindDiscoveryConfig: rc.createDiscoveryConfig, types.KindAuditQuery: rc.createAuditQuery, @@ -193,6 +194,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec types.KindUserTask: rc.updateUserTask, types.KindAutoUpdateConfig: rc.updateAutoUpdateConfig, types.KindAutoUpdateVersion: rc.updateAutoUpdateVersion, + types.KindDynamicWindowsDesktop: rc.updateDynamicWindowsDesktop, } rc.config = config @@ -891,6 +893,45 @@ func (rc *ResourceCommand) createWindowsDesktop(ctx context.Context, client *aut return nil } +func (rc *ResourceCommand) createDynamicWindowsDesktop(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { + wd, err := services.UnmarshalDynamicWindowsDesktop(raw.Raw) + if err != nil { + return trace.Wrap(err) + } + dynamicDesktopClient := client.DynamicDesktopClient() + if _, err := dynamicDesktopClient.CreateDynamicWindowsDesktop(ctx, wd); err != nil { + if trace.IsAlreadyExists(err) { + if !rc.force { + return trace.AlreadyExists("application %q already exists", wd.GetName()) + } + if _, err := dynamicDesktopClient.UpdateDynamicWindowsDesktop(ctx, wd); err != nil { + return trace.Wrap(err) + } + fmt.Printf("dynamic windows desktop %q has been updated\n", wd.GetName()) + return nil + } + return trace.Wrap(err) + } + + fmt.Printf("dynamic windows desktop %q has been updated\n", wd.GetName()) + return nil +} + +func (rc *ResourceCommand) updateDynamicWindowsDesktop(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { + wd, err := services.UnmarshalDynamicWindowsDesktop(raw.Raw) + if err != nil { + return trace.Wrap(err) + } + + dynamicDesktopClient := client.DynamicDesktopClient() + if _, err := dynamicDesktopClient.UpdateDynamicWindowsDesktop(ctx, wd); err != nil { + return trace.Wrap(err) + } + + fmt.Printf("dynamic windows desktop %q has been updated\n", wd.GetName()) + return nil +} + func (rc *ResourceCommand) createAppServer(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { appServer, err := services.UnmarshalAppServer(raw.Raw) if err != nil { @@ -1688,6 +1729,11 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *authclient.Client return trace.Wrap(err) } fmt.Printf("windows desktop service %q has been deleted\n", rc.ref.Name) + case types.KindDynamicWindowsDesktop: + if err = client.DynamicDesktopClient().DeleteDynamicWindowsDesktop(ctx, rc.ref.Name); err != nil { + return trace.Wrap(err) + } + fmt.Printf("dynamic windows desktop %q has been deleted\n", rc.ref.Name) case types.KindWindowsDesktop: desktops, err := client.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{Name: rc.ref.Name}) @@ -2461,6 +2507,41 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient return nil, trace.NotFound("Windows desktop %q not found", rc.ref.Name) } return &windowsDesktopCollection{desktops: out}, nil + case types.KindDynamicWindowsDesktop: + dynamicDesktopClient := client.DynamicDesktopClient() + if rc.ref.Name != "" { + desktop, err := dynamicDesktopClient.GetDynamicWindowsDesktop(ctx, rc.ref.Name) + if err != nil { + return nil, trace.Wrap(err) + } + return &dynamicWindowsDesktopCollection{ + desktops: []types.DynamicWindowsDesktop{desktop}, + }, nil + } + + pageToken := "" + desktops := make([]types.DynamicWindowsDesktop, 0, 100) + for { + d, next, err := dynamicDesktopClient.ListDynamicWindowsDesktops(ctx, 100, pageToken) + if err != nil { + return nil, trace.Wrap(err) + } + if rc.ref.Name == "" { + desktops = append(desktops, d...) + } else { + for _, desktop := range desktops { + if desktop.GetName() == rc.ref.Name { + desktops = append(desktops, desktop) + } + } + } + pageToken = next + if next == "" { + break + } + } + + return &dynamicWindowsDesktopCollection{desktops}, nil case types.KindToken: if rc.ref.Name == "" { tokens, err := client.GetTokens(ctx) diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index dca834a2e6e7f..cd7b8f7fc5de4 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -1419,6 +1419,10 @@ func TestCreateResources(t *testing.T) { kind: types.KindAutoUpdateVersion, create: testCreateAutoUpdateVersion, }, + { + kind: types.KindDynamicWindowsDesktop, + create: testCreateDynamicWindowsDesktop, + }, } for _, test := range tests { @@ -2359,6 +2363,35 @@ version: v1 )) } +func testCreateDynamicWindowsDesktop(t *testing.T, clt *authclient.Client) { + const resourceYAML = `kind: dynamic_windows_desktop +metadata: + name: test + revision: 3a43b44a-201e-4d7f-aef1-ae2f6d9811ed +spec: + addr: test +version: v1 +` + + // Create the resource. + resourceYAMLPath := filepath.Join(t.TempDir(), "resource.yaml") + require.NoError(t, os.WriteFile(resourceYAMLPath, []byte(resourceYAML), 0644)) + _, err := runResourceCommand(t, clt, []string{"create", resourceYAMLPath}) + require.NoError(t, err) + + // Get the resource + buf, err := runResourceCommand(t, clt, []string{"get", types.KindDynamicWindowsDesktop, "--format=json"}) + require.NoError(t, err) + resources := mustDecodeJSON[[]types.DynamicWindowsDesktopV1](t, buf) + require.Len(t, resources, 1) + + var expected types.DynamicWindowsDesktopV1 + require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected)) + expected.SetRevision(resources[0].GetRevision()) + + require.Empty(t, cmp.Diff([]types.DynamicWindowsDesktopV1{expected}, resources, protocmp.Transform())) +} + func TestPluginResourceWrapper(t *testing.T) { tests := []struct { name string diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index b02091d75a8a1..d7573c092a9d2 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -802,6 +802,11 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { ssh.Flag("no-resume", "Disable SSH connection resumption").Envar(noResumeEnvVar).BoolVar(&cf.DisableSSHResumption) ssh.Flag("relogin", "Permit performing an authentication attempt on a failed command").Default("true").BoolVar(&cf.Relogin) + resolve := app.Command("resolve", "Resolves an SSH host.") + resolve.Arg("host", "Remote hostname to resolve").Required().StringVar(&cf.UserHost) + resolve.Flag("quiet", "Quiet mode").Short('q').BoolVar(&cf.Quiet) + resolve.Flag("format", defaults.FormatFlagDescription(defaults.DefaultFormats...)).Short('f').Default(teleport.Text).EnumVar(&cf.Format, defaults.DefaultFormats...) + // Daemon service for teleterm client daemon := app.Command("daemon", "Daemon is the tsh daemon service.").Hidden() daemonStart := daemon.Command("start", "Starts tsh daemon service.").Hidden() @@ -1361,6 +1366,18 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { err = onVersion(&cf) case ssh.FullCommand(): err = onSSH(&cf) + case resolve.FullCommand(): + err = onResolve(&cf) + // If quiet was specified for this command and + // an error occurred, exit with a non-zero exit + // code without emitting any other messaging. + // In this case, the command was likely invoked + // via a Match exec block from an SSH config and + // if no matches were found, we should not add + // additional spam to stderr. + if err != nil && cf.Quiet { + err = trace.Wrap(&common.ExitCodeError{Code: 1}) + } case latencySSH.FullCommand(): err = onSSHLatency(&cf) case benchSSH.FullCommand(): @@ -3595,6 +3612,95 @@ func runLocalCommand(hostLogin string, command []string) error { return cmd.Run() } +// onResolve executes `tsh resolve`, a command that +// attempts to resolve a single host from a provided +// hostname. The host information provided may be +// interpolated by proxy templates and converted +// from a hostname into a fuzzy search, or predicate query. +// Errors are returned if unable to connect to the cluster, +// no matching hosts were found, or multiple matching hosts +// were found. This is primarily meant to be used as a command +// for a match exec block in an SSH config. +func onResolve(cf *CLIConf) error { + tc, err := makeClient(cf) + if err != nil { + return trace.Wrap(err) + } + + req := proto.ListUnifiedResourcesRequest{ + Kinds: []string{types.KindNode}, + Labels: tc.Labels, + SearchKeywords: tc.SearchKeywords, + PredicateExpression: tc.PredicateExpression, + UseSearchAsRoles: tc.UseSearchAsRoles, + SortBy: types.SortBy{Field: types.ResourceKind}, + // Limit to 2 so we can check for an ambiguous result + Limit: 2, + } + + // If no search criteria were explicitly provided, then match exclusively + // on the hostname of the server. Otherwise, this would end up listing + // the first two servers that the user has access to and yield unexpected results. + if len(tc.Labels) == 0 && len(tc.SearchKeywords) == 0 && tc.PredicateExpression == "" { + req.PredicateExpression = fmt.Sprintf(`name == "%s"`, tc.Host) + } + + // Only enable the re-authentication behavior if not invoked with `-q`. When + // in quiet mode, this command is likely being invoked via ssh and + // the login prompt will not be able to be presented to users anyway. + executor := client.RetryWithRelogin + if cf.Quiet { + executor = func(ctx context.Context, teleportClient *client.TeleportClient, f func() error, option ...client.RetryWithReloginOption) error { + return f() + } + } + + var page []*types.EnrichedResource + if err := executor(cf.Context, tc, func() error { + clt, err := tc.ConnectToCluster(cf.Context) + if err != nil { + return trace.Wrap(err) + } + + defer clt.Close() + + page, _, err = apiclient.GetUnifiedResourcePage(cf.Context, clt.AuthClient, &req) + if err != nil { + return trace.Wrap(err) + } + + return nil + }); err != nil { + return trace.Wrap(err) + } + + switch len(page) { + case 1: + case 0: + return trace.NotFound("no matching hosts found") + default: + return trace.BadParameter("multiple matching hosts found") + } + + if cf.Quiet { + return nil + } + + format := strings.ToLower(cf.Format) + switch format { + case teleport.Text, "": + printNodesAsText(cf.Stdout(), []types.Server{page[0].ResourceWithLabels.(types.Server)}, true) + case teleport.JSON: + utils.WriteJSON(cf.Stdout(), page[0].ResourceWithLabels) + case teleport.YAML: + utils.WriteYAML(cf.Stdout(), page[0].ResourceWithLabels) + default: + return trace.BadParameter("unsupported format %q", cf.Format) + } + + return nil +} + // onSSH executes 'tsh ssh' command func onSSH(cf *CLIConf) error { tc, err := makeClient(cf) diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 0cc5e07fc9aab..e7662c5dd03ec 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -4012,6 +4012,25 @@ func setCopyStdout(stdout io.Writer) CliOption { func setHomePath(path string) CliOption { return func(cf *CLIConf) error { cf.HomePath = path + // The TSHConfig is populated prior to applying any options, and + // if the home directory is being overridden, then any proxy templates + // and aliases that may have been loaded from the default home directory + // should be returned to default to avoid altering tests run from machines + // that may have a tsh config file present in the home directory. + cf.TSHConfig = client.TSHConfig{} + return nil + } +} + +func setTSHConfig(cfg client.TSHConfig) CliOption { + return func(cf *CLIConf) error { + for _, template := range cfg.ProxyTemplates { + if err := template.Check(); err != nil { + return err + } + } + + cf.TSHConfig = cfg return nil } } @@ -6527,3 +6546,175 @@ func TestRolesToString(t *testing.T) { }) } } + +// TestResolve tests that host resolution works for various inputs and +// that proxy templates are respected. +func TestResolve(t *testing.T) { + modules.SetTestModules(t, &modules.TestModules{TestBuildType: modules.BuildEnterprise}) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + accessRoleName := "access" + sshHostname := "test-ssh-server" + + accessUser, err := types.NewUser(accessRoleName) + require.NoError(t, err) + accessUser.SetRoles([]string{accessRoleName}) + + user, err := user.Current() + require.NoError(t, err) + accessUser.SetLogins([]string{user.Username}) + + traits := map[string][]string{ + constants.TraitLogins: {user.Username}, + } + accessUser.SetTraits(traits) + + connector := mockConnector(t) + rootServerOpts := []testserver.TestServerOptFunc{ + testserver.WithBootstrap(connector, accessUser), + testserver.WithHostname(sshHostname), + testserver.WithClusterName(t, "root"), + testserver.WithSSHPublicAddrs("127.0.0.1:0"), + testserver.WithConfig(func(cfg *servicecfg.Config) { + cfg.SSH.Enabled = true + cfg.SSH.PublicAddrs = []utils.NetAddr{cfg.SSH.Addr} + cfg.SSH.DisableCreateHostUser = true + cfg.SSH.Labels = map[string]string{ + "animal": "llama", + "env": "dev", + } + }), + } + rootServer := testserver.MakeTestServer(t, rootServerOpts...) + + node := testserver.MakeTestServer(t, + testserver.WithConfig(func(cfg *servicecfg.Config) { + cfg.SetAuthServerAddresses(rootServer.Config.AuthServerAddresses()) + cfg.Hostname = "second-node" + cfg.Auth.Enabled = false + cfg.Proxy.Enabled = false + cfg.SSH.Enabled = true + cfg.SSH.DisableCreateHostUser = true + cfg.SSH.Labels = map[string]string{ + "animal": "shark", + "env": "dev", + } + })) + + rootProxyAddr, err := rootServer.ProxyWebAddr() + require.NoError(t, err) + + require.EventuallyWithT(t, func(t *assert.CollectT) { + found, err := rootServer.GetAuthServer().GetNodes(ctx, apidefaults.Namespace) + if !assert.NoError(t, err) || !assert.Len(t, found, 2) { + return + } + }, 10*time.Second, 100*time.Millisecond) + + tmpHomePath := t.TempDir() + rootAuth := rootServer.GetAuthServer() + + err = Run(ctx, []string{ + "login", + "--insecure", + "--proxy", rootProxyAddr.String(), + "--user", user.Username, + }, setHomePath(tmpHomePath), setMockSSOLogin(rootAuth, accessUser, connector.GetName())) + require.NoError(t, err) + + tests := []struct { + name string + hostname string + quiet bool + assertion require.ErrorAssertionFunc + }{ + { + name: "resolved without using templates", + hostname: sshHostname, + assertion: require.NoError, + }, + { + name: "resolved via predicate from template", + hostname: "2.3.4.5", + assertion: require.NoError, + }, + { + name: "resolved via search from template", + hostname: "llama.example.com:3023", + assertion: require.NoError, + }, + { + name: "no matching host", + hostname: "asdf", + assertion: func(tt require.TestingT, err error, i ...interface{}) { + require.Error(tt, err, i...) + require.ErrorContains(tt, err, "no matching hosts", i...) + }, + }, + { + name: "quiet prevents output", + hostname: node.Config.Hostname, + quiet: true, + assertion: require.NoError, + }, + { + name: "multiple matching hosts", + hostname: "dev.example.com", + assertion: func(tt require.TestingT, err error, i ...interface{}) { + require.Error(tt, err, i...) + require.ErrorContains(tt, err, "multiple matching hosts", i...) + }, + }, + } + + for _, test := range tests { + test := test + ctx := context.Background() + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + stdout := &output{buf: bytes.Buffer{}} + stderr := &output{buf: bytes.Buffer{}} + + args := []string{"resolve"} + if test.quiet { + args = append(args, "-q") + } + args = append(args, test.hostname) + + err := Run(ctx, args, + setHomePath(tmpHomePath), + setTSHConfig(client.TSHConfig{ + ProxyTemplates: client.ProxyTemplates{ + { + Template: `^([0-9\.]+):\d+$`, + Query: `labels["animal"] == "llama"`, + }, + { + Template: `^(.*).example.com:\d+$`, + Search: "$1", + }, + }, + }), + func(conf *CLIConf) error { + conf.overrideStdin = &bytes.Buffer{} + conf.OverrideStdout = stdout + conf.overrideStderr = stderr + return nil + }, + ) + + test.assertion(t, err) + if err != nil { + return + } + + if test.quiet { + require.Empty(t, stdout.String()) + } else { + require.Contains(t, stdout.String(), sshHostname) + } + }) + } +} diff --git a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.test.tsx b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.test.tsx index 93aaf318946f4..97fc6d979408b 100644 --- a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.test.tsx +++ b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.test.tsx @@ -240,7 +240,7 @@ function getMockedContexts() { }; jest - .spyOn(integrationService, 'deployAwsOidcService') + .spyOn(integrationService, 'deployDatabaseServices') .mockResolvedValue('dashboard-url'); jest.spyOn(teleCtx.databaseService, 'fetchDatabases').mockResolvedValue({ diff --git a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx index 48c013f4e7359..51f2137f4f4d1 100644 --- a/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx +++ b/web/packages/teleport/src/Discover/Database/DeployService/AutoDeploy/AutoDeploy.tsx @@ -126,22 +126,24 @@ export function AutoDeploy({ toggleDeployMethod }: DeployServiceProp) { agentMeta.awsIntegration.spec.roleArn ); + const deployment = { + region: dbMeta.awsRegion, + accountId: awsAccountId, + taskRoleArn, + deployments: [ + { + vpcId: dbMeta.awsVpcId, + subnetIds: selectedSubnetIds, + securityGroups: selectedSecurityGroups, + }, + ], + }; + if (wantAutoDiscover) { setAttempt({ status: 'processing' }); integrationService - .deployDatabaseServices(integrationName, { - region: dbMeta.awsRegion, - accountId: awsAccountId, - taskRoleArn, - deployments: [ - { - vpcId: dbMeta.awsVpcId, - subnetIds: selectedSubnetIds, - securityGroups: selectedSecurityGroups, - }, - ], - }) + .deployDatabaseServices(integrationName, deployment) .then(url => { setAttempt({ status: 'success' }); setSvcDeployedAwsUrl(url); @@ -155,15 +157,7 @@ export function AutoDeploy({ toggleDeployMethod }: DeployServiceProp) { } else { setAttempt({ status: 'processing' }); integrationService - .deployAwsOidcService(integrationName, { - deploymentMode: 'database-service', - region: dbMeta.awsRegion, - subnetIds: selectedSubnetIds, - taskRoleArn, - securityGroups: selectedSecurityGroups, - vpcId: dbMeta.awsVpcId, - accountId: awsAccountId, - }) + .deployDatabaseServices(integrationName, deployment) // The user is still technically in the "processing" // state, because after this call succeeds, we will // start pinging for the newly registered db diff --git a/web/packages/teleterm/electron.vite.config.mts b/web/packages/teleterm/electron.vite.config.mts index 42a7d81eb44d6..926fc20b307cb 100644 --- a/web/packages/teleterm/electron.vite.config.mts +++ b/web/packages/teleterm/electron.vite.config.mts @@ -17,7 +17,6 @@ */ import path from 'node:path'; -import { existsSync, readFileSync } from 'node:fs'; import { defineConfig, externalizeDepsPlugin, UserConfig } from 'electron-vite'; @@ -120,31 +119,6 @@ const config = defineConfig(env => { }, }; - if (env.mode === 'development') { - if (process.env.VITE_HTTPS_KEY && process.env.VITE_HTTPS_CERT) { - config.renderer.server.https = { - key: readFileSync(process.env.VITE_HTTPS_KEY), - cert: readFileSync(process.env.VITE_HTTPS_CERT), - }; - } else { - const certsDirectory = path.resolve(rootDirectory, 'web/certs'); - - if (!existsSync(certsDirectory)) { - throw new Error( - 'Could not find SSL certificates. Please follow web/README.md to generate certificates.' - ); - } - - const keyPath = path.resolve(certsDirectory, 'server.key'); - const certPath = path.resolve(certsDirectory, 'server.crt'); - - config.renderer.server.https = { - key: readFileSync(keyPath), - cert: readFileSync(certPath), - }; - } - } - return config; }); diff --git a/web/packages/teleterm/src/mainProcess/windowsManager.ts b/web/packages/teleterm/src/mainProcess/windowsManager.ts index daa0935122c4c..513e31573400c 100644 --- a/web/packages/teleterm/src/mainProcess/windowsManager.ts +++ b/web/packages/teleterm/src/mainProcess/windowsManager.ts @@ -362,7 +362,7 @@ export class WindowsManager { * */ function getWindowUrl(isDev: boolean): string { if (isDev) { - return 'https://localhost:8080/'; + return 'http://localhost:8080/'; } // The returned URL is percent-encoded.