diff --git a/lib/config/configuration.go b/lib/config/configuration.go index f695c813847d6..b4c4f2e3b99b5 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -1940,11 +1940,13 @@ func applyWindowsDesktopConfig(fc *FileConfig, cfg *servicecfg.Config) error { return trace.Wrap(err) } cfg.WindowsDesktop.ShowDesktopWallpaper = fc.WindowsDesktop.ShowDesktopWallpaper - cfg.WindowsDesktop.Hosts, err = utils.AddrsFromStrings(fc.WindowsDesktop.Hosts, defaults.RDPListenPort) - if err != nil { - return trace.Wrap(err) + if len(fc.WindowsDesktop.ADHosts) > 0 { + log.Warnln("hosts field is deprecated, prefer static_hosts instead") } - cfg.WindowsDesktop.NonADHosts, err = utils.AddrsFromStrings(fc.WindowsDesktop.NonADHosts, defaults.RDPListenPort) + if len(fc.WindowsDesktop.NonADHosts) > 0 { + log.Warnln("non_ad_hosts field is deprecated, prefer static_hosts instead") + } + cfg.WindowsDesktop.StaticHosts, err = staticHostsWithAddress(fc.WindowsDesktop) if err != nil { return trace.Wrap(err) } @@ -2018,6 +2020,37 @@ func applyWindowsDesktopConfig(fc *FileConfig, cfg *servicecfg.Config) error { return nil } +func staticHostsWithAddress(ws WindowsDesktopService) ([]servicecfg.WindowsHost, error) { + var hostsWithAddress []servicecfg.WindowsHost + var cfgHosts []WindowsHost + cfgHosts = append(cfgHosts, ws.StaticHosts...) + for _, host := range ws.NonADHosts { + cfgHosts = append(cfgHosts, WindowsHost{ + Address: host, + AD: false, + }) + } + for _, host := range ws.ADHosts { + cfgHosts = append(cfgHosts, WindowsHost{ + Address: host, + AD: true, + }) + } + for _, host := range cfgHosts { + addr, err := utils.ParseHostPortAddr(host.Address, defaults.RDPListenPort) + if err != nil { + return nil, trace.BadParameter("invalid addr %q", host.Address) + } + hostsWithAddress = append(hostsWithAddress, servicecfg.WindowsHost{ + Name: host.Name, + Address: *addr, + Labels: host.Labels, + AD: host.AD, + }) + } + return hostsWithAddress, nil +} + // applyTracingConfig applies file configuration for the "tracing_service" section. func applyTracingConfig(fc *FileConfig, cfg *servicecfg.Config) error { // Tracing is enabled. diff --git a/lib/config/configuration_test.go b/lib/config/configuration_test.go index ad4b73901e38e..d2417708a75c1 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -541,7 +541,7 @@ func TestConfigReading(t *testing.T) { ListenAddress: "tcp://windows_desktop", }, PublicAddr: apiutils.Strings([]string{"winsrv.example.com:3028", "no-port.winsrv.example.com"}), - Hosts: apiutils.Strings([]string{"win.example.com:3389", "no-port.win.example.com"}), + ADHosts: apiutils.Strings([]string{"win.example.com:3389", "no-port.win.example.com"}), }, Tracing: TracingService{ EnabledFlag: "yes", @@ -1647,7 +1647,7 @@ func makeConfigFixture() string { ListenAddress: "tcp://windows_desktop", }, PublicAddr: apiutils.Strings([]string{"winsrv.example.com:3028", "no-port.winsrv.example.com"}), - Hosts: apiutils.Strings([]string{"win.example.com:3389", "no-port.win.example.com"}), + ADHosts: apiutils.Strings([]string{"win.example.com:3389", "no-port.win.example.com"}), } // Tracing service. @@ -2128,7 +2128,7 @@ func TestWindowsDesktopService(t *testing.T) { desc: "NOK - invalid static host addr", expectError: require.Error, mutate: func(fc *FileConfig) { - fc.WindowsDesktop.Hosts = []string{"badscheme://foo:1:2"} + fc.WindowsDesktop.ADHosts = []string{"badscheme://foo:1:2"} }, }, { @@ -2160,7 +2160,7 @@ func TestWindowsDesktopService(t *testing.T) { desc: "NOK - hosts specified but ldap not specified", expectError: require.Error, mutate: func(fc *FileConfig) { - fc.WindowsDesktop.Hosts = []string{"127.0.0.1:3389"} + fc.WindowsDesktop.ADHosts = []string{"127.0.0.1:3389"} fc.WindowsDesktop.LDAP = LDAPConfig{ Addr: "", } @@ -2170,7 +2170,7 @@ func TestWindowsDesktopService(t *testing.T) { desc: "OK - hosts specified and ldap specified", expectError: require.NoError, mutate: func(fc *FileConfig) { - fc.WindowsDesktop.Hosts = []string{"127.0.0.1:3389"} + fc.WindowsDesktop.ADHosts = []string{"127.0.0.1:3389"} fc.WindowsDesktop.LDAP = LDAPConfig{ Addr: "something", } @@ -2180,7 +2180,7 @@ func TestWindowsDesktopService(t *testing.T) { desc: "OK - no hosts specified and ldap not specified", expectError: require.NoError, mutate: func(fc *FileConfig) { - fc.WindowsDesktop.Hosts = []string{} + fc.WindowsDesktop.ADHosts = []string{} fc.WindowsDesktop.LDAP = LDAPConfig{ Addr: "", } @@ -2190,7 +2190,7 @@ func TestWindowsDesktopService(t *testing.T) { desc: "OK - no hosts specified and ldap specified", expectError: require.NoError, mutate: func(fc *FileConfig) { - fc.WindowsDesktop.Hosts = []string{} + fc.WindowsDesktop.ADHosts = []string{} fc.WindowsDesktop.LDAP = LDAPConfig{ Addr: "something", } @@ -2250,7 +2250,7 @@ func TestWindowsDesktopService(t *testing.T) { mutate: func(fc *FileConfig) { fc.WindowsDesktop.EnabledFlag = "yes" fc.WindowsDesktop.ListenAddress = "0.0.0.0:3028" - fc.WindowsDesktop.Hosts = []string{"127.0.0.1:3389"} + fc.WindowsDesktop.ADHosts = []string{"127.0.0.1:3389"} fc.WindowsDesktop.LDAP = LDAPConfig{ Addr: "something", } diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index 16ec2e1465ff0..8bbb628cf2f6b 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -2202,13 +2202,20 @@ type WindowsDesktopService struct { PKIDomain string `yaml:"pki_domain"` // Discovery configures desktop discovery via LDAP. Discovery LDAPDiscoveryConfig `yaml:"discovery,omitempty"` - // Hosts is a list of static, AD-connected Windows hosts. This gives users + // ADHosts is a list of static, AD-connected Windows hosts. This gives users // a way to specify AD-connected hosts that won't be found by the filters // specified in `discovery` (or if `discovery` is omitted). - Hosts []string `yaml:"hosts,omitempty"` + // + // Deprecated: prefer StaticHosts instead. + ADHosts []string `yaml:"hosts,omitempty"` // NonADHosts is a list of standalone Windows hosts that are not // jointed to an Active Directory domain. + // + // Deprecated: prefer StaticHosts instead. NonADHosts []string `yaml:"non_ad_hosts,omitempty"` + // StaticHosts is a list of Windows hosts (both AD-connected and standalone). + // User can specify name for each host and labels specific to it. + StaticHosts []WindowsHost `yaml:"static_hosts,omitempty"` // 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. @@ -2217,9 +2224,13 @@ type WindowsDesktopService struct { // Check checks whether the WindowsDesktopService is valid or not func (wds *WindowsDesktopService) Check() error { - if len(wds.Hosts) > 0 && wds.LDAP.Addr == "" { - return trace.BadParameter("if hosts are specified in the windows_desktop_service, " + - "the ldap configuration for their corresponding Active Directory domain controller must also be specified") + hasAD := len(wds.ADHosts) > 0 || slices.ContainsFunc(wds.StaticHosts, func(host WindowsHost) bool { + return host.AD + }) + + if hasAD && wds.LDAP.Addr == "" { + return trace.BadParameter("if Active Directory hosts are specified in the windows_desktop_service, " + + "the ldap configuration must also be specified") } if wds.Discovery.BaseDN != "" && wds.LDAP.Addr == "" { @@ -2230,7 +2241,7 @@ func (wds *WindowsDesktopService) Check() error { return nil } -// WindowsHostLabelRule describes how a set of labels should be a applied to +// WindowsHostLabelRule describes how a set of labels should be applied to // a Windows host. type WindowsHostLabelRule struct { // Match is a regexp that is checked against the Windows host's DNS name. @@ -2240,6 +2251,19 @@ type WindowsHostLabelRule struct { Labels map[string]string `yaml:"labels"` } +// WindowsHost describes single host in configuration +type WindowsHost struct { + // Name of the host + Name string `yaml:"name"` + // Address of the host, with an optional port. + // 10.1.103.4 or 10.1.103.4:3389, for example. + Address string `yaml:"addr"` + // Labels is the set of labels to apply to this host + Labels map[string]string `yaml:"labels"` + // AD tells if host is part of Active Directory domain + AD bool `yaml:"ad"` +} + // LDAPConfig is the LDAP connection parameters. type LDAPConfig struct { // Addr is the host:port of the LDAP server (typically port 389). diff --git a/lib/service/desktop.go b/lib/service/desktop.go index eddf621997053..97fd0df33714c 100644 --- a/lib/service/desktop.go +++ b/lib/service/desktop.go @@ -222,8 +222,7 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(log *logrus. Heartbeat: desktop.HeartbeatConfig{ HostUUID: cfg.HostUUID, PublicAddr: publicAddr, - StaticHosts: cfg.WindowsDesktop.Hosts, - NonADHosts: cfg.WindowsDesktop.NonADHosts, + StaticHosts: cfg.WindowsDesktop.StaticHosts, OnHeartbeat: process.OnHeartbeat(teleport.ComponentWindowsDesktop), }, ShowDesktopWallpaper: cfg.WindowsDesktop.ShowDesktopWallpaper, diff --git a/lib/service/servicecfg/windows.go b/lib/service/servicecfg/windows.go index 8c01349422c4f..ba27a7e56159e 100644 --- a/lib/service/servicecfg/windows.go +++ b/lib/service/servicecfg/windows.go @@ -45,16 +45,9 @@ type WindowsDesktopConfig struct { // Discovery configures automatic desktop discovery via LDAP. Discovery LDAPDiscoveryConfig - // Hosts is an optional list of static Windows hosts to expose through this + // StaticHosts is an optional list of static Windows hosts to expose through this // service. - // Hosts is an optional list of static, AD-connected Windows hosts. This gives users - // a way to specify AD-connected hosts that won't be found by the filters - // specified in Discovery (or if Discovery is omitted). - Hosts []utils.NetAddr - - // NonADHosts is an optional list of static Windows hosts to expose through this - // service. These hosts are not part of Active Directory. - NonADHosts []utils.NetAddr + StaticHosts []WindowsHost // ConnLimiter limits the connection and request rates. ConnLimiter limiter.Config @@ -63,6 +56,18 @@ type WindowsDesktopConfig struct { Labels map[string]string } +// WindowsHost is configuration for single Windows desktop host +type WindowsHost struct { + // Name that will be used in the Teleport UI + Name string + // Address of the remote Windows host + Address utils.NetAddr + // AD is true if the host is part of the Active Directory domain + AD bool + // Labels to be applied to the host + Labels map[string]string +} + // LDAPDiscoveryConfig is LDAP discovery configuration for windows desktop discovery service. type LDAPDiscoveryConfig struct { // BaseDN is the base DN to search for desktops. diff --git a/lib/srv/desktop/windows_server.go b/lib/srv/desktop/windows_server.go index 30cb97c54d25f..eea0838ae4c3c 100644 --- a/lib/srv/desktop/windows_server.go +++ b/lib/srv/desktop/windows_server.go @@ -46,6 +46,7 @@ import ( "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/reversetunnel" + "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/srv" @@ -205,10 +206,8 @@ type HeartbeatConfig struct { PublicAddr string // OnHeartbeat is called after each heartbeat attempt. OnHeartbeat func(error) - // StaticHosts is an optional list of AD-connected static Windows hosts to register. - StaticHosts []utils.NetAddr - // NonADHosts is an optional list of static Windows hosts to register, that are not part of Active Directory. - NonADHosts []utils.NetAddr + // StaticHosts is an optional list of static Windows hosts to register + StaticHosts []servicecfg.WindowsHost } func (cfg *WindowsServiceConfig) checkAndSetDiscoveryDefaults() error { @@ -398,7 +397,7 @@ func NewWindowsService(cfg WindowsServiceConfig) (*WindowsService, error) { if err := s.startDesktopDiscovery(); err != nil { return nil, trace.Wrap(err) } - } else if len(s.cfg.Heartbeat.StaticHosts) == 0 && len(s.cfg.Heartbeat.NonADHosts) == 0 { + } else if len(s.cfg.Heartbeat.StaticHosts) == 0 { s.cfg.Log.Warnln("desktop discovery via LDAP is disabled, and no hosts are defined in the configuration; there will be no Windows desktops available to connect") } else { s.cfg.Log.Infoln("desktop discovery via LDAP is disabled, set 'base_dn' to enable") @@ -598,25 +597,21 @@ func (s *WindowsService) startServiceHeartbeat() error { // service itself is running. func (s *WindowsService) startStaticHostHeartbeats() error { for _, host := range s.cfg.Heartbeat.StaticHosts { - if err := s.startStaticHostHeartbeat(host, false); err != nil { + if err := s.startStaticHostHeartbeat(host); err != nil { return err } } - for _, host := range s.cfg.Heartbeat.NonADHosts { - if err := s.startStaticHostHeartbeat(host, true); err != nil { - return trace.Wrap(err) - } - } return nil } -func (s *WindowsService) startStaticHostHeartbeat(host utils.NetAddr, nonAD bool) error { +// startStaticHostHeartbeats spawns heartbeat goroutine for single host +func (s *WindowsService) startStaticHostHeartbeat(host servicecfg.WindowsHost) error { heartbeat, err := srv.NewHeartbeat(srv.HeartbeatConfig{ Context: s.closeCtx, Component: teleport.ComponentWindowsDesktop, Mode: srv.HeartbeatModeWindowsDesktop, Announcer: s.cfg.AccessPoint, - GetServerInfo: s.staticHostHeartbeatInfo(host, s.cfg.HostLabelsFn, nonAD), + GetServerInfo: s.staticHostHeartbeatInfo(host, s.cfg.HostLabelsFn), KeepAlivePeriod: apidefaults.ServerKeepAliveTTL(), AnnouncePeriod: apidefaults.ServerAnnounceTTL/2 + utils.RandomDuration(apidefaults.ServerAnnounceTTL/10), CheckPeriod: 5 * time.Minute, @@ -1102,20 +1097,25 @@ func (s *WindowsService) getServiceHeartbeatInfo() (types.Resource, error) { // staticHostHeartbeatInfo generates the Windows Desktop resource // for heartbeating statically defined hosts -func (s *WindowsService) staticHostHeartbeatInfo(netAddr utils.NetAddr, - getHostLabels func(string) map[string]string, nonAD bool, +func (s *WindowsService) staticHostHeartbeatInfo(host servicecfg.WindowsHost, + getHostLabels func(string) map[string]string, ) func() (types.Resource, error) { return func() (types.Resource, error) { - addr := netAddr.String() - name, err := s.nameForStaticHost(addr) - if err != nil { - return nil, trace.Wrap(err) - } - // for static hosts, we match against the host's addr, - // as the name is a randomly generated UUID + addr := host.Address.String() labels := getHostLabels(addr) + for k, v := range host.Labels { + labels[k] = v + } + name := host.Name + if name == "" { + var err error + name, err = s.nameForStaticHost(addr) + if err != nil { + return nil, trace.Wrap(err) + } + } labels[types.OriginLabel] = types.OriginConfigFile - labels[types.ADLabel] = strconv.FormatBool(!nonAD) + labels[types.ADLabel] = strconv.FormatBool(host.AD) desktop, err := types.NewWindowsDesktopV3( name, labels, @@ -1123,7 +1123,7 @@ func (s *WindowsService) staticHostHeartbeatInfo(netAddr utils.NetAddr, Addr: addr, Domain: s.cfg.Domain, HostID: s.cfg.Heartbeat.HostUUID, - NonAD: nonAD, + NonAD: !host.AD, }) if err != nil { return nil, trace.Wrap(err)