Skip to content

Commit

Permalink
Add labels to Windows Desktop Service, add endpoint for searching them (
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanclark committed Sep 23, 2022
1 parent e06fe81 commit 79527ef
Show file tree
Hide file tree
Showing 28 changed files with 1,064 additions and 733 deletions.
2 changes: 2 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2653,6 +2653,8 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque
resources[i] = respResource.GetKubeService()
case types.KindWindowsDesktop:
resources[i] = respResource.GetWindowsDesktop()
case types.KindWindowsDesktopService:
resources[i] = respResource.GetWindowsDesktopService()
case types.KindKubernetesCluster:
resources[i] = respResource.GetKubeCluster()
default:
Expand Down
1,520 changes: 801 additions & 719 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,8 @@ message PaginatedResource {
types.WindowsDesktopV3 WindowsDesktop = 5 [(gogoproto.jsontag) = "windows_desktop,omitempty"];
// KubeCluster represents a KubeCluster resource.
types.KubernetesClusterV3 KubeCluster = 6 [(gogoproto.jsontag) = "kube_cluster,omitempty"];
// WindowsDesktopService represents a WindowsDesktopServiceV3 resource.
types.WindowsDesktopServiceV3 WindowsDesktopService = 8 [(gogoproto.jsontag) = "windows_desktop_service,omitempty"];
}
}

Expand Down
34 changes: 29 additions & 5 deletions api/types/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,24 @@ type WindowsDesktopService interface {
ProxiedService
}

type WindowsDesktopServices []WindowsDesktopService

// AsResources returns windows desktops as type resources with labels.
func (s WindowsDesktopServices) AsResources() []ResourceWithLabels {
resources := make([]ResourceWithLabels, 0, len(s))
for _, server := range s {
resources = append(resources, ResourceWithLabels(server))
}
return resources
}

var _ WindowsDesktopService = &WindowsDesktopServiceV3{}

// NewWindowsDesktopServiceV3 creates a new WindowsDesktopServiceV3 resource.
func NewWindowsDesktopServiceV3(name string, spec WindowsDesktopServiceSpecV3) (*WindowsDesktopServiceV3, error) {
func NewWindowsDesktopServiceV3(meta Metadata, spec WindowsDesktopServiceSpecV3) (*WindowsDesktopServiceV3, error) {
s := &WindowsDesktopServiceV3{
ResourceHeader: ResourceHeader{
Metadata: Metadata{
Name: name,
},
Metadata: meta,
},
Spec: spec,
}
Expand Down Expand Up @@ -129,7 +138,8 @@ func (s *WindowsDesktopServiceV3) GetHostname() string {
// MatchSearch goes through select field values and tries to
// match against the list of search values.
func (s *WindowsDesktopServiceV3) MatchSearch(values []string) bool {
return MatchSearch(nil, values, nil)
fieldVals := append(utils.MapToStrings(s.GetAllLabels()), s.GetName(), s.GetHostname())
return MatchSearch(fieldVals, values, nil)
}

// WindowsDesktop represents a Windows desktop host.
Expand Down Expand Up @@ -346,3 +356,17 @@ type ListWindowsDesktopsRequest struct {
Labels map[string]string
SearchKeywords []string
}

// ListWindowsDesktopServicesResponse is a response type to ListWindowsDesktopServices.
type ListWindowsDesktopServicesResponse struct {
DesktopServices []WindowsDesktopService
NextKey string
}

// ListWindowsDesktopServicesRequest is a request type to ListWindowsDesktopServices.
type ListWindowsDesktopServicesRequest struct {
Limit int
StartKey, PredicateExpression string
Labels map[string]string
SearchKeywords []string
}
13 changes: 13 additions & 0 deletions api/types/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,19 @@ func (r ResourcesWithLabels) AsWindowsDesktops() ([]WindowsDesktop, error) {
return desktops, nil
}

// AsWindowsDesktopServices converts each resource into type WindowsDesktop.
func (r ResourcesWithLabels) AsWindowsDesktopServices() ([]WindowsDesktopService, error) {
desktopServices := make([]WindowsDesktopService, 0, len(r))
for _, resource := range r {
desktopService, ok := resource.(WindowsDesktopService)
if !ok {
return nil, trace.BadParameter("expected types.WindowsDesktopService, got: %T", resource)
}
desktopServices = append(desktopServices, desktopService)
}
return desktopServices, nil
}

// AsKubeClusters converts each resource into type KubeCluster.
func (r ResourcesWithLabels) AsKubeClusters() ([]KubeCluster, error) {
clusters := make([]KubeCluster, 0, len(r))
Expand Down
4 changes: 3 additions & 1 deletion api/types/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,9 @@ func TestMatchSearch_ResourceSpecific(t *testing.T) {
name: "desktop service",
searchNotDefined: true,
newResource: func() ResourceWithLabels {
desktopService, err := NewWindowsDesktopServiceV3("_", WindowsDesktopServiceSpecV3{
desktopService, err := NewWindowsDesktopServiceV3(Metadata{
Name: "foo",
}, WindowsDesktopServiceSpecV3{
Addr: "_",
TeleportVersion: "_",
})
Expand Down
7 changes: 6 additions & 1 deletion docs/pages/includes/desktop-access/desktop-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ windows_desktop_service:
environment: dev
- match: '^.*\.prod\.example\.com$'
labels:
environment: prod
environment: prod

# Labels to attach to the Windows Desktop Service. This is used internally, so
# any custom labels added won't affect the Windows hosts.
labels:
teleport.internal/resource-id: "resource-id"
2 changes: 2 additions & 0 deletions lib/auth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,8 @@ type Cache interface {
ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error)
// ListWindowsDesktops returns a paginated list of windows desktops.
ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error)
// ListWindowsDesktopServices returns a paginated list of windows desktops.
ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error)

// GetInstaller gets installer resource for this cluster
GetInstaller(ctx context.Context, name string) (types.Installer, error)
Expand Down
16 changes: 16 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3172,6 +3172,22 @@ func (a *Server) ListResources(ctx context.Context, req proto.ListResourcesReque
NextKey: wResp.NextKey,
}, nil
}
if req.ResourceType == types.KindWindowsDesktopService {
wResp, err := a.ListWindowsDesktopServices(ctx, types.ListWindowsDesktopServicesRequest{
Limit: int(req.Limit),
StartKey: req.StartKey,
PredicateExpression: req.PredicateExpression,
Labels: req.Labels,
SearchKeywords: req.SearchKeywords,
})
if err != nil {
return nil, trace.Wrap(err)
}
return &types.ListResourcesResponse{
Resources: types.WindowsDesktopServices(wResp.DesktopServices).AsResources(),
NextKey: wResp.NextKey,
}, nil
}
return a.Cache.ListResources(ctx, req)
}

Expand Down
13 changes: 10 additions & 3 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1242,7 +1242,7 @@ func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResou
// https://github.com/gravitational/teleport/pull/1224
actionVerbs = []string{types.VerbList}

case types.KindDatabaseServer, types.KindAppServer, types.KindKubeService, types.KindWindowsDesktop:
case types.KindDatabaseServer, types.KindAppServer, types.KindKubeService, types.KindWindowsDesktop, types.KindWindowsDesktopService:

default:
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
Expand Down Expand Up @@ -1325,9 +1325,11 @@ func (r resourceChecker) CanAccess(resource types.Resource) error {
return r.CheckAccess(rr.GetDatabase(), mfaParams)
case types.Database:
return r.CheckAccess(rr, mfaParams)
case types.Server:
return r.CheckAccess(rr, mfaParams)
case types.WindowsDesktop:
return r.CheckAccess(rr, mfaParams)
case types.Server:
case types.WindowsDesktopService:
return r.CheckAccess(rr, mfaParams)
default:
return trace.BadParameter("could not check access to resource type %T", r)
Expand Down Expand Up @@ -1403,7 +1405,7 @@ func (k *kubeChecker) CanAccess(resource types.Resource) error {
// newResourceAccessChecker creates a resourceAccessChecker for the provided resource type
func (a *ServerWithRoles) newResourceAccessChecker(resource string) (resourceAccessChecker, error) {
switch resource {
case types.KindAppServer, types.KindDatabaseServer, types.KindWindowsDesktop:
case types.KindAppServer, types.KindDatabaseServer, types.KindWindowsDesktop, types.KindWindowsDesktopService:
return &resourceChecker{AccessChecker: a.context.Checker}, nil
case types.KindKubeService:
return newKubeChecker(a.context), nil
Expand Down Expand Up @@ -1531,6 +1533,11 @@ func (a *ServerWithRoles) ListWindowsDesktops(ctx context.Context, req types.Lis
return nil, trace.NotImplemented(notImplementedMessage)
}

// ListWindowsDesktopServices not implemented: can only be called locally.
func (a *ServerWithRoles) ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error) {
return nil, trace.NotImplemented(notImplementedMessage)
}

func (a *ServerWithRoles) UpsertAuthServer(s types.Server) error {
if err := a.action(apidefaults.Namespace, types.KindAuthServer, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
Expand Down
5 changes: 5 additions & 0 deletions lib/auth/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,11 @@ func (c *Client) ListWindowsDesktops(ctx context.Context, req types.ListWindowsD
return nil, trace.NotImplemented(notImplementedMessage)
}

// ListWindowsDesktopServices not implemented: can only be called locally.
func (c *Client) ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error) {
return nil, trace.NotImplemented(notImplementedMessage)
}

// DeleteAllUsers not implemented: can only be called locally.
func (c *Client) DeleteAllUsers() error {
return trace.NotImplemented(notImplementedMessage)
Expand Down
7 changes: 7 additions & 0 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3997,6 +3997,13 @@ func (g *GRPCServer) ListResources(ctx context.Context, req *proto.ListResources
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktop{WindowsDesktop: desktop}}
case types.KindWindowsDesktopService:
desktopService, ok := resource.(*types.WindowsDesktopServiceV3)
if !ok {
return nil, trace.BadParameter("windows desktop service has invalid type %T", resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktopService{WindowsDesktopService: desktopService}}
case types.KindKubernetesCluster:
cluster, ok := resource.(*types.KubernetesClusterV3)
if !ok {
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/join_ec2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ func TestHostUniqueCheck(t *testing.T) {
{
role: types.RoleWindowsDesktop,
upserter: func(name string) {
wds, err := types.NewWindowsDesktopServiceV3(instance1.account+"-"+instance1.instanceID,
wds, err := types.NewWindowsDesktopServiceV3(types.Metadata{Name: instance1.account + "-" + instance1.instanceID},
types.WindowsDesktopServiceSpecV3{
Addr: "localhost:3028",
TeleportVersion: "10.2.2",
Expand Down
13 changes: 13 additions & 0 deletions lib/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2100,6 +2100,19 @@ func (c *Cache) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDe
return rg.windowsDesktops.ListWindowsDesktops(ctx, req)
}

// ListWindowsDesktopServices returns all registered Windows desktop hosts.
func (c *Cache) ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error) {
ctx, span := c.Tracer.Start(ctx, "cache/ListWindowsDesktopServices")
defer span.End()

rg, err := c.read()
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
return rg.windowsDesktops.ListWindowsDesktopServices(ctx, req)
}

// ListResources is a part of auth.Cache implementation
func (c *Cache) ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) {
ctx, span := c.Tracer.Start(ctx, "cache/ListResources")
Expand Down
7 changes: 7 additions & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -1475,6 +1475,13 @@ func applyWindowsDesktopConfig(fc *FileConfig, cfg *service.Config) error {
})
}

if fc.WindowsDesktop.Labels != nil {
cfg.WindowsDesktop.Labels = make(map[string]string)
for k, v := range fc.WindowsDesktop.Labels {
cfg.WindowsDesktop.Labels[k] = v
}
}

return nil
}

Expand Down
2 changes: 2 additions & 0 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,8 @@ func (m *Metrics) MTLSEnabled() bool {
// WindowsDesktopService contains configuration for windows_desktop_service.
type WindowsDesktopService struct {
Service `yaml:",inline"`
// Labels are the configured windows deesktops service labels.
Labels map[string]string `yaml:"labels,omitempty"`
// PublicAddr is a list of advertised public addresses of this service.
PublicAddr apiutils.Strings `yaml:"public_addr,omitempty"`
// LDAP is the LDAP connection parameters.
Expand Down
1 change: 1 addition & 0 deletions lib/service/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,7 @@ type WindowsDesktopConfig struct {
ConnLimiter limiter.Config
// HostLabels specifies rules that are used to apply labels to Windows hosts.
HostLabels HostLabelRules
Labels map[string]string
}

type LDAPDiscoveryConfig struct {
Expand Down
1 change: 1 addition & 0 deletions lib/service/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(log *logrus.
ConnLimiter: connLimiter,
LockWatcher: lockWatcher,
AuthClient: conn.Client,
Labels: cfg.WindowsDesktop.Labels,
HostLabelsFn: cfg.WindowsDesktop.HostLabels.LabelsForHost,
Heartbeat: desktop.HeartbeatConfig{
HostUUID: cfg.HostUUID,
Expand Down
1 change: 1 addition & 0 deletions lib/services/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type WindowsDesktops interface {
DeleteWindowsDesktop(ctx context.Context, hostID, name string) error
DeleteAllWindowsDesktops(context.Context) error
ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error)
ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error)
}

// MarshalWindowsDesktop marshals the WindowsDesktop resource to JSON.
Expand Down
56 changes: 56 additions & 0 deletions lib/services/local/desktops.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,62 @@ func (s *WindowsDesktopService) ListWindowsDesktops(ctx context.Context, req typ
}, nil
}

func (s *WindowsDesktopService) ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error) {
reqLimit := req.Limit
if reqLimit <= 0 {
return nil, trace.BadParameter("nonpositive parameter limit")
}

rangeStart := backend.Key(windowsDesktopServicesPrefix, req.StartKey)
rangeEnd := backend.RangeEnd(backend.Key(windowsDesktopServicesPrefix, ""))
filter := services.MatchResourceFilter{
ResourceKind: types.KindWindowsDesktopService,
Labels: req.Labels,
SearchKeywords: req.SearchKeywords,
PredicateExpression: req.PredicateExpression,
}

// Get most limit+1 results to determine if there will be a next key.
maxLimit := reqLimit + 1
var desktopServices []types.WindowsDesktopService
if err := backend.IterateRange(ctx, s.Backend, rangeStart, rangeEnd, maxLimit, func(items []backend.Item) (stop bool, err error) {
for _, item := range items {
if len(desktopServices) == maxLimit {
break
}

desktop, err := services.UnmarshalWindowsDesktopService(item.Value,
services.WithResourceID(item.ID), services.WithExpires(item.Expires))
if err != nil {
return false, trace.Wrap(err)
}

switch match, err := services.MatchResourceByFilters(desktop, filter, nil /* ignore dup matches */); {
case err != nil:
return false, trace.Wrap(err)
case match:
desktopServices = append(desktopServices, desktop)
}
}

return len(desktopServices) == maxLimit, nil
}); err != nil {
return nil, trace.Wrap(err)
}

var nextKey string
if len(desktopServices) > reqLimit {
nextKey = backend.GetPaginationKey(desktopServices[len(desktopServices)-1])
// Truncate the last item that was used to determine next row existence.
desktopServices = desktopServices[:reqLimit]
}

return &types.ListWindowsDesktopServicesResponse{
DesktopServices: desktopServices,
NextKey: nextKey,
}, nil
}

const (
windowsDesktopsPrefix = "windowsDesktop"
)
3 changes: 3 additions & 0 deletions lib/services/local/presence.go
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,9 @@ func (s *PresenceService) listResources(ctx context.Context, req proto.ListResou
case types.KindNode:
keyPrefix = []string{nodesPrefix, req.Namespace}
unmarshalItemFunc = backendItemToServer(types.KindNode)
case types.KindWindowsDesktopService:
keyPrefix = []string{windowsDesktopServicesPrefix, req.Namespace}
unmarshalItemFunc = backendItemToServer(types.KindWindowsDesktopService)
case types.KindKubeService:
keyPrefix = []string{kubeServicesPrefix}
unmarshalItemFunc = backendItemToServer(types.KindKubeService)
Expand Down
2 changes: 1 addition & 1 deletion lib/services/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func MatchResourceByFilters(resource types.ResourceWithLabels, filter MatchResou
// the user is wanting to filter the contained resource ie. KubeClusters, Application, and Database.
resourceKey := ResourceSeenKey{}
switch filter.ResourceKind {
case types.KindNode, types.KindWindowsDesktop, types.KindKubernetesCluster:
case types.KindNode, types.KindWindowsDesktop, types.KindWindowsDesktopService, types.KindKubernetesCluster:
specResource = resource
resourceKey.name = specResource.GetName()

Expand Down
2 changes: 2 additions & 0 deletions lib/services/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,8 @@ func (set RoleSet) checkAccess(r AccessCheckable, mfa AccessMFAParams, matchers
case types.KindWindowsDesktop:
getRoleLabels = types.Role.GetWindowsDesktopLabels
additionalDeniedMessage = "Confirm Windows user."
case types.KindWindowsDesktopService:
getRoleLabels = types.Role.GetWindowsDesktopLabels
default:
return trace.BadParameter("cannot match labels for kind %v", r.GetKind())
}
Expand Down
Loading

0 comments on commit 79527ef

Please sign in to comment.