-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Add dynamic windows desktops #46738
Add dynamic windows desktops #46738
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty big PR. It might make sense to do the proto changes in a dedicated. PR.
// desktop host. If HostID is not specified, all Windows desktops with | ||
// specified Name will be deleted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the info about HostID, as there is no HostID here.
} | ||
|
||
// DynamicWindowsDesktopFilter are filters to apply when searching for windows desktops. | ||
message DynamicWindowsDesktopFilter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to duplicate this? Can we use the existing filter message and just ignore the HostID?
if d.Spec.Addr == "" { | ||
return trace.BadParameter("DynamicWindowsDesktopV1.Spec missing Addr field") | ||
} | ||
|
||
// We use SNI to identify the desktop to route a connection to, | ||
// and '.' will add an extra subdomain, preventing Teleport from | ||
// correctly establishing TLS connections. | ||
if name := d.GetName(); strings.Contains(name, ".") { | ||
return trace.BadParameter("invalid name %q: desktop names cannot contain periods", name) | ||
} | ||
|
||
d.setStaticFields() | ||
if err := d.ResourceHeader.CheckAndSetDefaults(); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
|
||
if d.Spec.ScreenSize != nil { | ||
if d.Spec.ScreenSize.Width > MaxRDPScreenWidth || d.Spec.ScreenSize.Height > MaxRDPScreenHeight { | ||
return trace.BadParameter("invalid screen size %dx%d (maximum %dx%d)", | ||
d.Spec.ScreenSize.Width, d.Spec.ScreenSize.Height, MaxRDPScreenWidth, MaxRDPScreenHeight) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a lot of copy-paste from the original desktop resource. I'm worried about the two diverging. Any suggestions for sharing more code?
} | ||
|
||
// GetFieldVals returns list of select field values. | ||
func (s DynamicWindowsDesktops) GetFieldVals(field string) ([]string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this used for?
@@ -262,6 +262,9 @@ const ( | |||
// ComponentWindowsDesktop is a Windows desktop access server. | |||
ComponentWindowsDesktop = "windows_desktop" | |||
|
|||
// ComponentDynamicWindowsDesktop is a Windows desktop access server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is exactly the same as the one on ComponentWindowsDesktop
. I would expect the godoc to help me understand what is different about a dynamic desktop.
Then again, if this is just used for logging, then you can probably use the original component field.
@@ -2385,7 +2385,8 @@ type WindowsDesktopService struct { | |||
// HostLabels optionally applies labels to Windows hosts for RBAC. | |||
// A host can match multiple rules and will get a union of all | |||
// the matched labels. | |||
HostLabels []WindowsHostLabelRule `yaml:"host_labels,omitempty"` | |||
HostLabels []WindowsHostLabelRule `yaml:"host_labels,omitempty"` | |||
ResourceMatchers []ResourceMatcher `yaml:"resources,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add godoc for the new field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with Zac, there is a lot going on here. It might be best to split this into PRs that operate on a single layer: proto, backend storage, rpc implementation, tctl, resource watcher, windows desktop server changes.
// GetDynamicWindowsDesktops returns all registered Windows desktop hosts matching the supplied filter. | ||
rpc GetDynamicWindowsDesktops(types.DynamicWindowsDesktopFilter) returns (GetDynamicWindowsDesktopsResponse); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use a List RPC instead of a GetAll RPC that returns an array of desktops? Is this separate RPC needed? Should these resource be Listed via ListResources or ListUnifiedResources?
rpc CreateDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (google.protobuf.Empty); | ||
// UpdateDynamicWindowsDesktop updates an existing Windows desktop host. | ||
rpc UpdateDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (google.protobuf.Empty); | ||
// UpsertDynamicWindowsDesktop updates a Windows desktop host, creating it if it doesn't exist. | ||
rpc UpsertDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (google.protobuf.Empty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create, Update, and Upsert should all return the resource instead of empty.
rpc CreateDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (google.protobuf.Empty); | |
// UpdateDynamicWindowsDesktop updates an existing Windows desktop host. | |
rpc UpdateDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (google.protobuf.Empty); | |
// UpsertDynamicWindowsDesktop updates a Windows desktop host, creating it if it doesn't exist. | |
rpc UpsertDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (google.protobuf.Empty); | |
rpc CreateDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (types.DynamicWindowsDesktopV1); | |
// UpdateDynamicWindowsDesktop updates an existing Windows desktop host. | |
rpc UpdateDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (types.DynamicWindowsDesktopV1); | |
// UpsertDynamicWindowsDesktop updates a Windows desktop host, creating it if it doesn't exist. | |
rpc UpsertDynamicWindowsDesktop(types.DynamicWindowsDesktopV1) returns (types.DynamicWindowsDesktopV1); |
// DeleteAllDynamicWindowsDesktops removes all registered Windows desktop hosts. | ||
rpc DeleteAllDynamicWindowsDesktops(google.protobuf.Empty) returns (google.protobuf.Empty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not need to be exposed via an RPC. The main purpose of DeleteAll is to satisfy the cache interface, and that can all be done with the storage service.
// DeleteAllDynamicWindowsDesktops removes all registered Windows desktop hosts. | |
rpc DeleteAllDynamicWindowsDesktops(google.protobuf.Empty) returns (google.protobuf.Empty); |
// DynamicWindowsDesktopV1 represents a dynamic windows host for desktop access. | ||
message DynamicWindowsDesktopV1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name DynamicWindowsDesktopV1 made me initially think that something about this desktop was dynamic, not that it is a windows desktop host that is discovered dynamically. All of the comments refer to this as a dynamic windows desktop host - perhaps we should consider renaming this to WindowsDesktopHostV1?
func (a *ServerWithRoles) CreateDynamicWindowsDesktop(ctx context.Context, s types.DynamicWindowsDesktop) error { | ||
if err := a.action(apidefaults.Namespace, types.KindDynamicWindowsDesktop, types.VerbCreate); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
return a.authServer.CreateDynamicWindowsDesktop(ctx, s) | ||
} | ||
|
||
// UpdateDynamicWindowsDesktop updates an existing dynamic windows desktop host. | ||
func (a *ServerWithRoles) UpdateDynamicWindowsDesktop(ctx context.Context, s types.DynamicWindowsDesktop) error { | ||
if err := a.action(apidefaults.Namespace, types.KindDynamicWindowsDesktop, types.VerbUpdate); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
|
||
existing, err := a.authServer.GetDynamicWindowsDesktops(ctx, | ||
types.DynamicWindowsDesktopFilter{Name: s.GetName()}) | ||
if err != nil { | ||
return trace.Wrap(err) | ||
} | ||
if len(existing) == 0 { | ||
return trace.NotFound("no dynamic windows desktops with Name %s", s.GetName()) | ||
} | ||
|
||
if err := a.checkAccessToDynamicWindowsDesktop(existing[0]); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
if err := a.checkAccessToDynamicWindowsDesktop(s); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
return a.authServer.UpdateDynamicWindowsDesktop(ctx, s) | ||
} | ||
|
||
// UpsertDynamicWindowsDesktop updates a dynamic windows desktop resource, creating it if it doesn't exist. | ||
func (a *ServerWithRoles) UpsertDynamicWindowsDesktop(ctx context.Context, s types.DynamicWindowsDesktop) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these be called by humans or by a desktop_service? Should there be an admin actions check?
select { | ||
case desktops := <-watcher.DynamicWindowsDesktopsC: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to handle when the watcher is closed?
select { | |
case desktops := <-watcher.DynamicWindowsDesktopsC: | |
select { | |
case <-watcher.Done(): | |
case desktops := <-watcher.DynamicWindowsDesktopsC: |
labels := make(map[string]string) | ||
maps.Copy(labels, dynamicDesktop.GetAllLabels()) | ||
labels[types.OriginLabel] = types.OriginDynamic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
labels := make(map[string]string) | |
maps.Copy(labels, dynamicDesktop.GetAllLabels()) | |
labels[types.OriginLabel] = types.OriginDynamic | |
desktopLabels := dynamicDesktop.GetAllLabels() | |
labels := make(map[string]string, len(desktopLabels)+1) | |
maps.Copy(labels, desktopLabels) | |
labels[types.OriginLabel] = types.OriginDynamic |
|
||
fmt.Printf("labels %q", wd.GetAllLabels()) | ||
|
||
if err := client.UpsertDynamicWindowsDesktop(ctx, wd); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should call CreateDynamicWindowsDesktop unless --force has been provided.
@@ -153,6 +153,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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add an equivalent update handler that calls UpdatDynamicWindowsDesktop when tctl edit is used for this resource.
@@ -882,6 +883,22 @@ 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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also update the TestCreateResources and TestEditResources tests for this new resource
It was splitted to smaller PRs, closing this one |
This change adds new resource: dynamic windows desktop.
The difference between this new type and current windows desktop is that it doesn't have host UUID - it is meant to be picked up by windows desktop services by matching labels similar to dynamic registration of apps and databases.