diff --git a/lib/auth/windows/windows.go b/lib/auth/windows/windows.go index 56ced358e8dfa..35ce269a3d2b1 100644 --- a/lib/auth/windows/windows.go +++ b/lib/auth/windows/windows.go @@ -132,7 +132,11 @@ func getCertRequest(req *GenerateCredentialsRequest) (*certRequest, error) { // domain, with the assumption that some other windows_desktop_service // published a CRL there. crlDN := crlDN(req.ClusterName, req.LDAPConfig, req.CAType) - return &certRequest{csrPEM: csrPEM, crlEndpoint: fmt.Sprintf("ldap:///%s?certificateRevocationList?base?objectClass=cRLDistributionPoint", crlDN), keyDER: keyDER}, nil + return &certRequest{ + csrPEM: csrPEM, + crlEndpoint: fmt.Sprintf("ldap:///%s?certificateRevocationList?base?objectClass=cRLDistributionPoint", crlDN), + keyDER: keyDER, + }, nil } // AuthInterface is a subset of auth.ClientI diff --git a/lib/config/configuration.go b/lib/config/configuration.go index b2a28ed63fc72..08b51f59fb332 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -1848,6 +1848,8 @@ func applyWindowsDesktopConfig(fc *FileConfig, cfg *servicecfg.Config) error { CA: cert, } + cfg.WindowsDesktop.PKIDomain = fc.WindowsDesktop.PKIDomain + var hlrs []servicecfg.HostLabelRule for _, rule := range fc.WindowsDesktop.HostLabels { r, err := regexp.Compile(rule.Match) diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index 534b0b9528be2..c6173f32535c6 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -2374,6 +2374,12 @@ type WindowsDesktopService struct { ShowDesktopWallpaper bool `yaml:"show_desktop_wallpaper,omitempty"` // LDAP is the LDAP connection parameters. LDAP LDAPConfig `yaml:"ldap"` + // PKIDomain optionally configures a separate Active Directory domain + // for PKI operations. If empty, the domain from the LDAP config is used. + // This can be useful for cases where PKI is configured in a root domain + // but Teleport is used to provide access to users and computers in a child + // domain. + 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 diff --git a/lib/service/desktop.go b/lib/service/desktop.go index a379786e82bbe..9defd40c53d21 100644 --- a/lib/service/desktop.go +++ b/lib/service/desktop.go @@ -228,6 +228,7 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(log *logrus. }, ShowDesktopWallpaper: cfg.WindowsDesktop.ShowDesktopWallpaper, LDAPConfig: windows.LDAPConfig(cfg.WindowsDesktop.LDAP), + PKIDomain: cfg.WindowsDesktop.PKIDomain, DiscoveryBaseDN: cfg.WindowsDesktop.Discovery.BaseDN, DiscoveryLDAPFilters: cfg.WindowsDesktop.Discovery.Filters, DiscoveryLDAPAttributeLabels: cfg.WindowsDesktop.Discovery.LabelAttributes, diff --git a/lib/service/servicecfg/windows.go b/lib/service/servicecfg/windows.go index ce0da95aa7df5..8c01349422c4f 100644 --- a/lib/service/servicecfg/windows.go +++ b/lib/service/servicecfg/windows.go @@ -35,6 +35,12 @@ type WindowsDesktopConfig struct { ShowDesktopWallpaper bool // LDAP is the LDAP connection parameters. LDAP LDAPConfig + // PKIDomain optionally configures a separate Active Directory domain + // for PKI operations. If empty, the domain from the LDAP config is used. + // This can be useful for cases where PKI is configured in a root domain + // but Teleport is used to provide access to users and computers in a child + // domain. + PKIDomain string // Discovery configures automatic desktop discovery via LDAP. Discovery LDAPDiscoveryConfig diff --git a/lib/srv/desktop/windows_server.go b/lib/srv/desktop/windows_server.go index 15d64014fc55c..8e051a16bab37 100644 --- a/lib/srv/desktop/windows_server.go +++ b/lib/srv/desktop/windows_server.go @@ -176,6 +176,12 @@ type WindowsServiceConfig struct { // LDAPConfig contains parameters for connecting to an LDAP server. // LDAP functionality is disabled if Addr is empty. windows.LDAPConfig + // PKIDomain optionally configures a separate Active Directory domain + // for PKI operations. If empty, the domain from the LDAP config is used. + // This can be useful for cases where PKI is configured in a root domain + // but Teleport is used to provide access to users and computers in a child + // domain. + PKIDomain string // DiscoveryBaseDN is the base DN for searching for Windows Desktops. // Desktop discovery is disabled if this field is empty. DiscoveryBaseDN string @@ -352,15 +358,21 @@ func NewWindowsService(cfg WindowsServiceConfig) (*WindowsService, error) { auditCache: newSharedDirectoryAuditCache(), } + caLDAPConfig := s.cfg.LDAPConfig + if s.cfg.PKIDomain != "" { + caLDAPConfig.Domain = s.cfg.PKIDomain + } + s.cfg.Log.Infof("Windows PKI will be performed against %v", s.cfg.PKIDomain) + s.ca = windows.NewCertificateStoreClient(windows.CertificateStoreConfig{ AccessPoint: s.cfg.AccessPoint, - LDAPConfig: s.cfg.LDAPConfig, + LDAPConfig: caLDAPConfig, Log: s.cfg.Log, ClusterName: s.clusterName, LC: s.lc, }) - if s.cfg.LDAPConfig.Addr != "" { + if caLDAPConfig.Addr != "" { s.ldapConfigured = true // initialize LDAP - if this fails it will automatically schedule a retry. // we don't want to return an error in this case, because failure to start @@ -1196,6 +1208,14 @@ type generateCredentialsRequest struct { // Directory. See: // https://docs.microsoft.com/en-us/windows/security/identity-protection/smart-cards/smart-card-certificate-requirements-and-enumeration func (s *WindowsService) generateCredentials(ctx context.Context, request generateCredentialsRequest) (certDER, keyDER []byte, err error) { + // If PKI domain has been overridden, make sure we pass that through + // to the cert request, otherwise the CRL in the cert we issue will + // point at the wrong domain. + lc := s.cfg.LDAPConfig + if s.cfg.PKIDomain != "" { + lc.Domain = s.cfg.PKIDomain + } + return windows.GenerateWindowsDesktopCredentials(ctx, &windows.GenerateCredentialsRequest{ CAType: types.UserCA, Username: request.username, @@ -1203,7 +1223,7 @@ func (s *WindowsService) generateCredentials(ctx context.Context, request genera TTL: request.ttl, ClusterName: s.clusterName, ActiveDirectorySID: request.activeDirectorySID, - LDAPConfig: s.cfg.LDAPConfig, + LDAPConfig: lc, AuthClient: s.cfg.AuthClient, CreateUser: request.createUser, Groups: request.groups, diff --git a/lib/srv/desktop/windows_server_test.go b/lib/srv/desktop/windows_server_test.go index 10d63ed287128..ed412c5af805a 100644 --- a/lib/srv/desktop/windows_server_test.go +++ b/lib/srv/desktop/windows_server_test.go @@ -123,32 +123,45 @@ func TestGenerateCredentials(t *testing.T) { require.NoError(t, client.Close()) }) - w := &WindowsService{ - clusterName: clusterName, - cfg: WindowsServiceConfig{ - LDAPConfig: windows.LDAPConfig{ - Domain: domain, - }, - AuthClient: client, - }, - } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() for _, test := range []struct { name string activeDirectorySID string + cdp string + configure func(*WindowsServiceConfig) }{ { name: "no ad sid", activeDirectorySID: "", + cdp: `ldap:///CN=test,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=test,DC=example,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint`, }, { name: "with ad sid", activeDirectorySID: testSid, + cdp: `ldap:///CN=test,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=test,DC=example,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint`, + }, + { + name: "separate PKI domain", + activeDirectorySID: "", + configure: func(cfg *WindowsServiceConfig) { cfg.PKIDomain = "pki.example.com" }, + cdp: `ldap:///CN=test,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=pki,DC=example,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint`, }, } { + w := &WindowsService{ + clusterName: clusterName, + cfg: WindowsServiceConfig{ + LDAPConfig: windows.LDAPConfig{ + Domain: domain, + }, + AuthClient: client, + }, + } + if test.configure != nil { + test.configure(&w.cfg) + } + certb, keyb, err := w.generateCredentials(ctx, generateCredentialsRequest{ username: user, domain: domain, @@ -164,8 +177,7 @@ func TestGenerateCredentials(t *testing.T) { require.NotNil(t, cert) require.Equal(t, user, cert.Subject.CommonName) - require.Contains(t, cert.CRLDistributionPoints, - `ldap:///CN=test,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=test,DC=example,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint`) + require.ElementsMatch(t, cert.CRLDistributionPoints, []string{test.cdp}) foundKeyUsage := false foundAltName := false