Skip to content

Commit

Permalink
Label desktops based on the content of LDAP attributes
Browse files Browse the repository at this point in the history
This allows users to configure an optional set of LDAP attributes
which will be included in all LDAP queries. Teleport uses these
attributes when labeling desktops.

Updates #12326
  • Loading branch information
zmb3 committed Jun 1, 2022
1 parent 230692f commit 0b2f29a
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 19 deletions.
38 changes: 30 additions & 8 deletions docs/pages/desktop-access/rbac.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,15 @@ use wildcards (`"*"`) to match all desktop labels.

Windows desktops acquire labels in two ways:

1. The `host_labels` rules defined in the `windows_desktop_service` section of
your Teleport configuration file.
2. Automatic `teleport.dev/` labels applied by Teleport (for desktops discovered
via LDAP only)
1. Via the `host_labels` rules defined in the `windows_desktop_service` section
of your Teleport configuration file.
2. Via LDAP (for desktops discovered via LDAP only)

For example, the following `host_labels` configuration would apply the
`environment: dev` label to a Windows desktop named `test.dev.example.com`
and the `environment: prod` label to `desktop.prod.example.com`:
### With `host_labels`

The following `host_labels` configuration would apply the `environment: dev`
label to a Windows desktop named `test.dev.example.com` and the
`environment: prod` label to `desktop.prod.example.com`:

```yaml
host_labels:
Expand All @@ -76,7 +77,16 @@ host_labels:
environment: prod
```

For desktops discovered via LDAP, Teleport applies the following labels automatically:
### With LDAP

There are several ways that desktops discovered via LDAP can be labeled:

1. Automatic `teleport.dev/` labels, applied unconditionally
2. Custom `ldap/` labels, sourced from LDAP attributes and applied based on
discovery configuration

Teleport applies the following labels automatically to all desktops discovered
via LDAP:

| Label | LDAP Attribute | Example |
| ----------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------- |
Expand All @@ -88,6 +98,18 @@ For desktops discovered via LDAP, Teleport applies the following labels automati
| `teleport.dev/is_domain_controller` | `primaryGroupID` | `true` |
| `teleport.dev/ou` | Derived from `distinguishedName` | `OU=IT,DC=goteleport,DC=com` |

Additionally, users can configure LDAP attributes which will be converted into
Teleport labels. For example, consider the following configuration:

```yaml
discovery:
label_attributes:
- location
```

For a desktop with a `location` attribute of `Oakland`, Teleport would apply a
label with key `ldap/location` and value `Oakland`.

## Logins

The `windows_desktop_logins` role setting lists the Windows user accounts that
Expand Down
4 changes: 4 additions & 0 deletions docs/pages/includes/desktop-access/desktop-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ windows_desktop_service:
filters:
- '(location=Oakland)'
- '(!(primaryGroupID=516))' # exclude domain controllers
# (optional) LDAP attributes to convert into Teleport labels.
# The key of the label will be "ldap/" + the value of the attribute.
label_attributes:
- location
# Rules for applying labels to Windows hosts based on regular expressions
# matched against the host name. If multiple rules match, the desktop will
# get the union of all matching labels.
Expand Down
1 change: 1 addition & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,7 @@ func applyWindowsDesktopConfig(fc *FileConfig, cfg *service.Config) error {
return trace.BadParameter("WindowsDesktopService specifies invalid LDAP filter %q", filter)
}
}

cfg.WindowsDesktop.Discovery = fc.WindowsDesktop.Discovery

var err error
Expand Down
6 changes: 6 additions & 0 deletions lib/service/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,12 @@ type LDAPDiscoveryConfig struct {
// Filters are additional LDAP filters to apply to the search.
// See: https://ldap.com/ldap-filters/
Filters []string `yaml:"filters"`
// LabelAttributes are LDAP attributes to apply to hosts discovered
// via LDAP. Teleport labels hosts by prefixing the attribute with
// "ldap/" - for example, a value of "location" here would result in
// discovered desktops having a label with key "ldap/location" and
// the value being the value of the "location" attribute.
LabelAttributes []string `yaml:"label_attributes"`
}

// HostLabelRules is a collection of rules describing how to apply labels to hosts.
Expand Down
9 changes: 5 additions & 4 deletions lib/service/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,11 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(log *logrus.
StaticHosts: cfg.WindowsDesktop.Hosts,
OnHeartbeat: process.onHeartbeat(teleport.ComponentWindowsDesktop),
},
LDAPConfig: desktop.LDAPConfig(cfg.WindowsDesktop.LDAP),
DiscoveryBaseDN: cfg.WindowsDesktop.Discovery.BaseDN,
DiscoveryLDAPFilters: cfg.WindowsDesktop.Discovery.Filters,
Hostname: cfg.Hostname,
LDAPConfig: desktop.LDAPConfig(cfg.WindowsDesktop.LDAP),
DiscoveryBaseDN: cfg.WindowsDesktop.Discovery.BaseDN,
DiscoveryLDAPFilters: cfg.WindowsDesktop.Discovery.Filters,
DiscoveryLDAPAttributeLabels: cfg.WindowsDesktop.Discovery.LabelAttributes,
Hostname: cfg.Hostname,
})
if err != nil {
return trace.Wrap(err)
Expand Down
22 changes: 17 additions & 5 deletions lib/srv/desktop/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ func (s *WindowsService) getDesktopsFromLDAP() types.ResourcesWithLabelsMap {
filter := s.ldapSearchFilter()
s.cfg.Log.Debugf("searching for desktops with LDAP filter %v", filter)

entries, err := s.lc.readWithFilter(s.cfg.DiscoveryBaseDN, filter, computerAttribtes)
var attrs []string
attrs = append(attrs, computerAttribtes...)
attrs = append(attrs, s.cfg.DiscoveryLDAPAttributeLabels...)

entries, err := s.lc.readWithFilter(s.cfg.DiscoveryBaseDN, filter, attrs)
if trace.IsConnectionProblem(err) {
// If the connection was broken, re-initialize the LDAP client so that it's
// ready for the next reconcile loop. Return the last known set of desktops
Expand Down Expand Up @@ -180,26 +184,34 @@ func (s *WindowsService) deleteDesktop(ctx context.Context, r types.ResourceWith
return s.cfg.AuthClient.DeleteWindowsDesktop(ctx, d.GetHostID(), d.GetName())
}

func applyLabelsFromLDAP(entry *ldap.Entry, labels map[string]string) {
func (s *WindowsService) applyLabelsFromLDAP(entry *ldap.Entry, labels map[string]string) {
// apply common LDAP labels by default
labels[types.OriginLabel] = types.OriginDynamic

labels[types.TeleportNamespace+"/dns_host_name"] = entry.GetAttributeValue(attrDNSHostName)
labels[types.TeleportNamespace+"/computer_name"] = entry.GetAttributeValue(attrName)
labels[types.TeleportNamespace+"/os"] = entry.GetAttributeValue(attrOS)
labels[types.TeleportNamespace+"/os_version"] = entry.GetAttributeValue(attrOSVersion)

// attempt to compute the desktop's OU from its DN
dn := entry.GetAttributeValue(attrDistinguishedName)
cn := entry.GetAttributeValue(attrCommonName)

if len(dn) > 0 && len(cn) > 0 {
ou := strings.TrimPrefix(dn, "CN="+cn+",")
labels[types.TeleportNamespace+"/ou"] = ou
}

// label domain controllers
switch entry.GetAttributeValue(attrPrimaryGroupID) {
case writableDomainControllerGroupID, readOnlyDomainControllerGroupID:
labels[types.TeleportNamespace+"/is_domain_controller"] = "true"
}

// apply any custom labels per the discovery configuration
for _, attr := range s.cfg.DiscoveryLDAPAttributeLabels {
if v := entry.GetAttributeValue(attr); v != "" {
labels["ldap/"+attr] = v
}
}
}

// ldapEntryToWindowsDesktop generates the Windows Desktop resource
Expand All @@ -208,7 +220,7 @@ func (s *WindowsService) ldapEntryToWindowsDesktop(ctx context.Context, entry *l
hostname := entry.GetAttributeValue(attrDNSHostName)
labels := getHostLabels(hostname)
labels[types.TeleportNamespace+"/windows_domain"] = s.cfg.Domain
applyLabelsFromLDAP(entry, labels)
s.applyLabelsFromLDAP(entry, labels)

addrs, err := s.dnsResolver.LookupHost(ctx, hostname)
if err != nil || len(addrs) == 0 {
Expand Down
20 changes: 18 additions & 2 deletions lib/srv/desktop/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,34 @@ func TestAppliesLDAPLabels(t *testing.T) {
attrOSVersion: {"6.1"},
attrDistinguishedName: {"CN=foo,OU=IT,DC=goteleport,DC=com"},
attrCommonName: {"foo"},
"bar": {"baz"},
"quux": {""},
})
applyLabelsFromLDAP(entry, l)

s := &WindowsService{
cfg: WindowsServiceConfig{
DiscoveryLDAPAttributeLabels: []string{"bar"},
},
}
s.applyLabelsFromLDAP(entry, l)

// check default labels
require.Equal(t, l[types.OriginLabel], types.OriginDynamic)
require.Equal(t, l[types.TeleportNamespace+"/dns_host_name"], "foo.example.com")
require.Equal(t, l[types.TeleportNamespace+"/computer_name"], "foo")
require.Equal(t, l[types.TeleportNamespace+"/os"], "Windows Server")
require.Equal(t, l[types.TeleportNamespace+"/os_version"], "6.1")

// check OU label
require.Equal(t, l[types.TeleportNamespace+"/ou"], "OU=IT,DC=goteleport,DC=com")

// check custom labels
require.Equal(t, l["ldap/bar"], "baz")
require.Empty(t, l["ldap/quux"])
}

func TestLabelsDomainControllers(t *testing.T) {
s := &WindowsService{}
for _, test := range []struct {
desc string
entry *ldap.Entry
Expand Down Expand Up @@ -110,7 +126,7 @@ func TestLabelsDomainControllers(t *testing.T) {
} {
t.Run(test.desc, func(t *testing.T) {
l := make(map[string]string)
applyLabelsFromLDAP(test.entry, l)
s.applyLabelsFromLDAP(test.entry, l)

b, _ := strconv.ParseBool(l[types.TeleportNamespace+"/is_domain_controller"])
test.assert(t, b)
Expand Down
3 changes: 3 additions & 0 deletions lib/srv/desktop/windows_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ type WindowsServiceConfig struct {
// Windows Desktops. If multiple filters are specified, they are ANDed
// together into a single search.
DiscoveryLDAPFilters []string
// DiscoveryLDAPAttributeLabels are optional LDAP attributes to convert
// into Teleport labels.
DiscoveryLDAPAttributeLabels []string
// Hostname of the windows desktop service
Hostname string
}
Expand Down

0 comments on commit 0b2f29a

Please sign in to comment.