Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions docs/pages/enroll-resources/desktop-access/active-directory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -840,27 +840,47 @@ In order to perform NLA, Teleport's `windows_desktop_service` needs to be able
to make an outbound Kerberos connection to a key distribution center (KDC). This
is most commonly performed on TCP port 88.

By default, Teleport will assume that a KDC is available on the same host that
is specified in the `ldap` configuration's `addr` field, using the default
Kerberos port.
Teleport determines the address of the KDC by checking the following (in order of priority):

For example, with the following configuration, Teleport will attempt to perform
NLA against `example.com:88`.
1. If `kdc_address` is set, Teleport will use this address.
1. As of version 18.3.1, if `locate_server` is enabled and `kdc_address` is
not set, Teleport will attempt to discover the KDC address via DNS SRV records.
1. If neither `locate_server` or `kdc_address` is specified, Teleport will
assume that a KDC is available on the same host that is specified in the
`ldap` configuration's `addr` field.

For example, you can set the KDC address by specifying the `kdc_address`
in your Teleport configuration file.

```yaml
windows_desktop_service:
enabled: true
ldap:
addr: example.com:636
kdc_address: kdc.example.com # defaults to port 88 if unspecified
```

Alternatively, you can override the KDC address by specifying the `kdc_address`
in your Teleport configuration file.
If you're using the `locate_server` option, Teleport will perform a DNS
SRV lookup on the provided `domain` (e.g. `_kerberos._tcp.my-site._sites.example.com`).
NLA will be performed against the highest-priority and reachable KDC.

```yaml
windows_desktop_service:
enabled: true

locate_server:
enabled: true
site: "my-site" # optional
domain: example.com
```

If server location is disabled and `kdc_address` isn't specified, with the following configuration,
Teleport will attempt to perform NLA against `example.com:88`.

```yaml
windows_desktop_service:
enabled: true
kdc_address: kdc.example.com # defaults to port 88 if unspecified

ldap:
addr: example.com:636
```

To enable NLA, set the `TELEPORT_ENABLE_RDP_NLA` environment variable to `yes`
Expand Down
5 changes: 3 additions & 2 deletions docs/pages/includes/config-reference/desktop-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ windows_desktop_service:
pki_domain: root.example.com

# (optional) Configures the address of the Kerberos Key Distribution Center,
# which is used to support RDP Network Level Authentication (NLA).
# If empty, the LDAP address will be used instead.
# which is used to support RDP Network Level Authentication (NLA). When set,
# this field takes priority over locate_server. If empty and locate_server
# is disabled, the LDAP address will be used instead.
#
# example: kdc.example.com:88.
# The port is optional and defaults to port 88 if unspecified.
Expand Down
2 changes: 1 addition & 1 deletion lib/srv/desktop/rdp/rdpclient/client_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type Config struct {
// KDCAddr is the address of Key Distribution Center.
// This is used to support RDP Network Level Authentication (NLA)
// when connecting to hosts enrolled in Active Directory.
// This filed is not used when AD is false.
// This field is not used when AD is false.
KDCAddr string

// AD indicates whether the desktop is part of an Active Directory domain.
Expand Down
82 changes: 77 additions & 5 deletions lib/srv/desktop/windows_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"fmt"
"log/slog"
"net"
"os"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -59,6 +60,7 @@ import (
"github.com/gravitational/teleport/lib/srv/desktop/tdp"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/dns"
logutils "github.com/gravitational/teleport/lib/utils/log"
"github.com/gravitational/teleport/lib/utils/slices"
"github.com/gravitational/teleport/lib/winpki"
Expand Down Expand Up @@ -786,11 +788,9 @@ func (s *WindowsService) connectRDP(ctx context.Context, log *slog.Logger, tdpCo
}
log = log.With("computer_name", computerName)

kdcAddr := s.cfg.KDCAddr
if !desktop.NonAD() && kdcAddr == "" && s.cfg.LDAPConfig.Addr != "" {
if kdcAddr, err = utils.Host(s.cfg.LDAPConfig.Addr); err != nil {
return trace.Wrap(err, "KDC address is unspecified and LDAP address is invalid")
}
kdcAddr, err := s.getKDCAddress(ctx)
if err != nil {
return trace.Wrap(err, "getting KDC address")
}

nla := s.enableNLA && !desktop.NonAD()
Expand Down Expand Up @@ -1330,3 +1330,75 @@ func (s *WindowsService) runCRLUpdateLoop(tlsConfig *tls.Config) {
}
}
}

// getKDCAddress gets the KDC address that should be used for NLA in
// this priority order:
// 1. Explicitly specified kdc_address
// 2. If enabled, using locate_server DNS lookups
// 3. If all else fails, ldap's addr
func (s *WindowsService) getKDCAddress(ctx context.Context) (string, error) {
if s.cfg.KDCAddr != "" {
if s.cfg.LocateServer.Enabled {
s.cfg.Logger.WarnContext(ctx, "Both locate_server and kdc_address are set, kdc_address takes priority", "kdc_address", s.cfg.KDCAddr)
} else {
s.cfg.Logger.DebugContext(ctx, "Using hardcoded KDC address", "kdc_address", s.cfg.KDCAddr)
}
return s.cfg.KDCAddr, nil
}

if !s.cfg.LocateServer.Enabled && s.cfg.LDAPConfig.Addr != "" {
kdcAddr, err := utils.Host(s.cfg.LDAPConfig.Addr)
if err != nil {
return "", trace.Wrap(err, "KDC address is unspecified, locate server is disabled, and LDAP address is invalid")
}
s.cfg.Logger.DebugContext(ctx, "locate_server and kdc_address unspecified, assuming that KDC is available on the same host as LDAP", "address", s.cfg.LDAPConfig.Addr)
return kdcAddr, nil
}

s.cfg.Logger.DebugContext(
ctx,
"Looking for KDC server",
"domain", s.cfg.Domain,
"site", s.cfg.LocateServer.Site,
)

// In development environments, the system's default resolver is unlikely to be
// able to resolve the Active Directory SRV records needed for server location,
// so we allow overriding the resolver. If the TELEPORT_KDC_RESOLVER parameter
// is not set, the default resolver will be used.
resolver := dns.NewResolver(ctx, os.Getenv("TELEPORT_KDC_RESOLVER"), s.cfg.Logger)

servers, err := dns.LocateServerBySRV(
ctx,
s.cfg.Domain,
s.cfg.LocateServer.Site,
resolver,
"kerberos",
"", // Use port returned by SRV record
)
if err != nil {
return "", trace.Wrap(err, "locating KDC server")
}

if len(servers) == 0 {
return "", trace.NotFound("no KDC servers found for domain %q", s.cfg.Domain)
}

var lastErr error
for _, server := range servers {
conn, err := net.DialTimeout("tcp", server, 5*time.Second)
if conn != nil {
conn.Close()
}

if err == nil {
s.cfg.Logger.InfoContext(ctx, "Found KDC server", "server", server)
return server, nil
}
lastErr = err

s.cfg.Logger.InfoContext(ctx, "Error connecting to KDC server, trying next available server", "server", server, "error", err)
}

return "", trace.NotFound("no KDC servers responded successfully for domain %q: %v", s.cfg.Domain, lastErr)
}
28 changes: 18 additions & 10 deletions lib/winpki/locate.go → lib/utils/dns/locate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,37 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package winpki
package dns

import (
"cmp"
"context"
"net"
"strconv"

"github.com/gravitational/trace"
)

// locateLDAPServer looks up the LDAP server in an Active Directory
// environment by implementing the DNS-based discovery DC locator
// process.
// Resolver interface wraps the net.Resolver methods needed for testing
type Resolver interface {
LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error)
}

// LocateServerBySRV looks up a server of a given service and port
// in an Active Directory environment by implementing the
// DNS-based discovery DC locator process.
//
// See https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/dc-locator?tabs=dns-based-discovery
func locateLDAPServer(ctx context.Context, domain string, site string, resolver *net.Resolver) ([]string, error) {
func LocateServerBySRV(ctx context.Context, domain string, site string, resolver Resolver, service string, port string) ([]string, error) {
tryDomain := domain
if site != "" {
tryDomain = site + "._sites." + domain
}

_, records, err := resolver.LookupSRV(ctx, "ldap", "tcp", tryDomain)
_, records, err := resolver.LookupSRV(ctx, service, "tcp", tryDomain)
if err != nil && site != "" {
// If the site lookup fails, try the domain directly.
_, records, err = resolver.LookupSRV(ctx, "ldap", "tcp", domain)
_, records, err = resolver.LookupSRV(ctx, service, "tcp", domain)
}

if err != nil {
Expand All @@ -49,9 +56,10 @@ func locateLDAPServer(ctx context.Context, domain string, site string, resolver
// note: LookupSRV already returns records sorted by priority and takes in to account weights
var result []string
for _, record := range records {
// SRV records will likely return the insecure LDAP port,
// so we ignore it and hard code the LDAPS port.
result = append(result, net.JoinHostPort(record.Target, "636"))
// If a port has been passed, use that.
// If not, use the port returned by the SRV record.
usePort := cmp.Or(port, strconv.Itoa(int(record.Port)))
result = append(result, net.JoinHostPort(record.Target, usePort))
}

return result, nil
Expand Down
Loading
Loading