Skip to content

Commit

Permalink
windows ldaps port (gravitational#8932)
Browse files Browse the repository at this point in the history
* Connect to LDAP on port 636 (LDAPS)

Rather than connecting to LDAP on the insecure port and attempting
to upgrade the connection to TLS, simply connect on the LDAPS port.

* WIP

* check connection state

* check connection state

* printing certs

* confirming cert verf fails

* checkpoint

* updates config and new ldap client to make use of ldaps

* change VerifyCA to SkipVerifyCA

* checking for proper certificate in applyWindowsDesktopConfig

* fixes yaml value

* CR

* error wording change

* allow the user to use the system cert pool

* var change

* removing todo and renaming some fields

* refactors error messages and simplifies logic

Co-authored-by: Zac Bergquist <[email protected]>
  • Loading branch information
Isaiah Becker-Mayer and zmb3 authored Nov 12, 2021
1 parent bbfd13c commit 58c0e81
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 27 deletions.
5 changes: 1 addition & 4 deletions docs/pages/desktop-access/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ Desktop Access and log into a Windows desktop from that domain.

This guide requires you to have:

- An Active Directory domain
- [Active Directory Certificate Services (AD
CS)](https://docs.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/install-the-certification-authority)
installed in the domain
- An Active Directory domain, configured for LDAPS (Teleport requires an encrypted LDAP connection)
- Access to a Domain Controller
- LDAP credentials for Active Directory (usually the same credentials you use
to log into the Domain Controller)
Expand Down
23 changes: 15 additions & 8 deletions docs/pages/desktop-access/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,25 @@ that runs `windows_desktop_service`.

### Cannot Initialize LDAP over TLS

Teleport fails to start with an error similar to:
Teleport fails to connect to LDAP on startup. You may see errors similar to:

```text
LDAP Result Code 52 "Unavailable": 00000000: LdapErr: DSID-0C090F78, comment: Error initializing SSL/TLS, data 0, v2580\x00
```

**Solution:** Install Active Directory Certificate Services
or

```text
connecting to LDAP server: unable to read LDAP response packet: read tcp 172.18.0.5:35970->;172.18.0.4:636: read: connection reset by peer
```

This means that you do not have Active Directory Certificate Services (AD CS)
installed in your domain. AD CS provides the certificate for TLS connections to
LDAP and Teleport refuses to authenticate to LDAP over an insecure connection.
**Solution:** Enable LDAPS

You must [install AD
CS](https://docs.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/install-the-certification-authority)
on one of the Domain Controllers and reboot it.
This means you do not have an LDAP certificate installed on your LDAP servers.
You can resolve this by
[installing Active Directory Certificate Services](https://docs.microsoft.com/en-us/windows-server/networking/core-network-guide/cncg/server-certs/install-the-certification-authority)
(AD CS) or
[importing](https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority)
your own third party certificate. Note that Active Directory is
[extremely picky](https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority#requirements-for-an-ldaps-certificate)
so take care to generate your certificates correctly.
29 changes: 27 additions & 2 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package config
import (
"bufio"
"bytes"
"crypto/x509"
"io"
"io/ioutil"
"net"
Expand Down Expand Up @@ -1208,17 +1209,41 @@ func applyWindowsDesktopConfig(fc *FileConfig, cfg *service.Config) error {
}
ldapPassword, err := os.ReadFile(fc.WindowsDesktop.LDAP.PasswordFile)
if err != nil {
return trace.WrapWithMessage(err, "loading LDAP password from file %v",
return trace.WrapWithMessage(err, "loading the LDAP password from file %v",
fc.WindowsDesktop.LDAP.PasswordFile)
}

// If a CA file is provided but InsecureSkipVerify is also set to true, throw an
// error to make sure the user isn't making a critical security mistake (i.e. thinking
// that their LDAPS connection is being verified to be with the CA provided, but it isn't
// due to InsecureSkipVerify == true ).
if fc.WindowsDesktop.LDAP.DEREncodedCAFile != "" && fc.WindowsDesktop.LDAP.InsecureSkipVerify {
return trace.BadParameter("a CA file was provided but insecure_skip_verify was also set to true; confirm that you really want CA verification to be skipped by deleting or commenting-out the der_ca_file configuration value")
}

var cert *x509.Certificate
if fc.WindowsDesktop.LDAP.DEREncodedCAFile != "" {
rawCert, err := os.ReadFile(fc.WindowsDesktop.LDAP.DEREncodedCAFile)
if err != nil {
return trace.WrapWithMessage(err, "loading the LDAP CA from file %v", fc.WindowsDesktop.LDAP.DEREncodedCAFile)
}

cert, err = x509.ParseCertificate(rawCert)
if err != nil {
return trace.WrapWithMessage(err, "parsing the LDAP root CA file %v", fc.WindowsDesktop.LDAP.DEREncodedCAFile)
}
}

cfg.WindowsDesktop.LDAP = service.LDAPConfig{
Addr: fc.WindowsDesktop.LDAP.Addr,
Username: fc.WindowsDesktop.LDAP.Username,
Domain: fc.WindowsDesktop.LDAP.Domain,

// trim whitespace to protect against things like
// a leading tab character or trailing newline
Password: string(bytes.TrimSpace(ldapPassword)),
Password: string(bytes.TrimSpace(ldapPassword)),
InsecureSkipVerify: fc.WindowsDesktop.LDAP.InsecureSkipVerify,
CA: cert,
}

for _, rule := range fc.WindowsDesktop.HostLabels {
Expand Down
4 changes: 4 additions & 0 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -1325,4 +1325,8 @@ type LDAPConfig struct {
Username string `yaml:"username"`
// PasswordFile is a text file containing the password for LDAP authentication.
PasswordFile string `yaml:"password_file"`
// InsecureSkipVerify decides whether whether we skip verifying with the LDAP server's CA when making the LDAPS connection.
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
// DEREncodedCAFile is the filepath to an optional DER encoded CA cert to be used for verification (if InsecureSkipVerify is set to false).
DEREncodedCAFile string `yaml:"der_ca_file,omitempty"`
}
5 changes: 5 additions & 0 deletions lib/service/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package service

import (
"crypto/x509"
"fmt"
"io"
"net"
Expand Down Expand Up @@ -877,6 +878,10 @@ type LDAPConfig struct {
Username string
// Password for LDAP authentication.
Password string
// InsecureSkipVerify decides whether whether we skip verifying with the LDAP server's CA when making the LDAPS connection.
InsecureSkipVerify bool
// CA is an optional CA cert to be used for verification if InsecureSkipVerify is set to false.
CA *x509.Certificate
}

// Rewrite is a list of rewriting rules to apply to requests and responses.
Expand Down
10 changes: 6 additions & 4 deletions lib/srv/desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ Another wizard (AD DS configuration) will open:

#### AD CS

Next, we need Active Directory Certificate Services (AD CS) to enable TLS on
LDAP connections. From Server Manager, in the top-right select `Manage > Add
Roles and Features`. (Note: you won't be able to install both AD DS and AD CS
at the same time, they need to be separate).
Next, we'll install Active Directory Certificate Services (AD CS) to enable TLS
on LDAP connections. While AD CS is not strictly required, it is the easiest way
to generate a keypair and ensure that the server supports LDAPS. From Server
Manager, in the top-right select `Manage > Add Roles and Features`. (Note: you
won't be able to install both AD DS and AD CS at the same time, they need to be
separate).

In the wizard, select:
- Before you Begin: click `Next`
Expand Down
31 changes: 23 additions & 8 deletions lib/srv/desktop/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package desktop

import (
"crypto/tls"
"crypto/x509"
"fmt"

"github.com/go-ldap/ldap/v3"
Expand All @@ -33,17 +34,31 @@ type ldapClient struct {
// newLDAPClient connects to an LDAP server, authenticates and returns the
// client connection. Caller must close the client after using it.
func newLDAPClient(cfg LDAPConfig) (*ldapClient, error) {
con, err := ldap.Dial("tcp", cfg.Addr)
if err != nil {
return nil, trace.Wrap(err)
tlsConfig := &tls.Config{
InsecureSkipVerify: cfg.InsecureSkipVerify,
}
// TODO(awly): should we get a CA cert for the LDAP cert validation? Active
// Directory Certificate Services (their managed CA thingy) seems to be
// issuing those.
if err := con.StartTLS(&tls.Config{InsecureSkipVerify: true}); err != nil {
con.Close()

if !cfg.InsecureSkipVerify {
// Get the SystemCertPool, continue with an empty pool on error
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}

if cfg.CA != nil {
// Append our cert to the pool.
rootCAs.AddCert(cfg.CA)
}

// Supply our cert pool to TLS config for verification.
tlsConfig.RootCAs = rootCAs
}

con, err := ldap.DialURL("ldaps://"+cfg.Addr, ldap.DialWithTLSConfig(tlsConfig))
if err != nil {
return nil, trace.Wrap(err)
}

// TODO(zmb3): Active Directory, theoretically, supports cert-based
// authentication. Figure out the right certificate format and generate it
// with Teleport CA for authn here.
Expand Down
12 changes: 11 additions & 1 deletion lib/srv/desktop/windows_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ type WindowsServiceConfig struct {
// LDAPConfig contains parameters for connecting to an LDAP server.
type LDAPConfig struct {
// Addr is the LDAP server address in the form host:port.
// Standard port is 389.
// Standard port is 636 for LDAPS.
Addr string
// Domain is an Active Directory domain name, like "example.com".
Domain string
Expand All @@ -135,6 +135,10 @@ type LDAPConfig struct {
// Password is an LDAP password. Usually it's the same user password used
// for local logins on the Domain Controller.
Password string
// InsecureSkipVerify decides whether whether we skip verifying with the LDAP server's CA when making the LDAPS connection.
InsecureSkipVerify bool
// CA is an optional CA cert to be used for verification if InsecureSkipVerify is set to false.
CA *x509.Certificate
}

func (cfg LDAPConfig) check() error {
Expand Down Expand Up @@ -795,9 +799,15 @@ func (s *WindowsService) updateCA(ctx context.Context) error {
return nil
}

// updateCAInNTAuthStore records the Teleport user CA in the Windows store which records
// CAs that are eligible to issue smart card login certificates and perform client
// private key archival.
//
// This is equivalent to running `certutil –dspublish –f <PathToCertFile.cer> NTAuthCA`
func (s *WindowsService) updateCAInNTAuthStore(ctx context.Context, caDER []byte) error {
// Check if our CA is already in the store. The LDAP entry for NTAuth store
// is constant and it should always exist.
// TODO(zmb3): NTAuthCertificates may not exist, create it if necessary.
ntauthPath := ldapPath{"Configuration", "Services", "Public Key Services", "NTAuthCertificates"}
entries, err := s.lc.read(ntauthPath, "certificationAuthority", []string{"cACertificate"})
if err != nil {
Expand Down

0 comments on commit 58c0e81

Please sign in to comment.