Skip to content

Commit

Permalink
Merge branch 'master' into joerger/joerger/api-release-with-go
Browse files Browse the repository at this point in the history
  • Loading branch information
Joerger authored Oct 22, 2021
2 parents 0bbab8a + 747f370 commit c7e6323
Show file tree
Hide file tree
Showing 19 changed files with 115 additions and 41 deletions.
6 changes: 6 additions & 0 deletions docs/pages/desktop-access/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ windows_desktop_service:
# Plain text file containing the LDAP password for authentication.
# This is usually the same password you use to login to the Domain Controller.
password_file: /var/lib/ldap-pass
# (optional) settings for enabling automatic desktop discovery via LDAP
discovery:
# currently supported values:
# '*' to search from the root of the domain
# '' to disable desktop discovery
base_dn: '*'
# 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
9 changes: 9 additions & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (

"golang.org/x/crypto/ssh"

"github.com/go-ldap/ldap/v3"
"github.com/gravitational/trace"

"github.com/gravitational/teleport"
Expand Down Expand Up @@ -1183,6 +1184,14 @@ func applyWindowsDesktopConfig(fc *FileConfig, cfg *service.Config) error {
}
cfg.WindowsDesktop.ListenAddr = *listenAddr
}

for _, filter := range fc.WindowsDesktop.Discovery.Filters {
if _, err := ldap.CompileFilter(ldap.EscapeFilter(filter)); err != nil {
return trace.BadParameter("WindowsDesktopService specifies invalid LDAP filter %q", filter)
}
}
cfg.WindowsDesktop.Discovery = fc.WindowsDesktop.Discovery

var err error
cfg.WindowsDesktop.PublicAddrs, err = utils.AddrsFromStrings(fc.WindowsDesktop.PublicAddr, defaults.WindowsDesktopListenPort)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions lib/config/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,7 @@ func makeConfigFixture() string {
},
}

// Windows Desktop Service
conf.WindowsDesktop = WindowsDesktopService{
Service: Service{
EnabledFlag: "yes",
Expand Down
2 changes: 2 additions & 0 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -1314,6 +1314,8 @@ type WindowsDesktopService struct {
PublicAddr apiutils.Strings `yaml:"public_addr,omitempty"`
// LDAP is the LDAP connection parameters.
LDAP LDAPConfig `yaml:"ldap"`
// Discovery configures desktop discovery via LDAP.
Discovery service.LDAPDiscoveryConfig `yaml:"discovery,omitempty"`
// Hosts is a list of static Windows hosts connected to this service in
// gateway mode.
Hosts []string `yaml:"hosts,omitempty"`
Expand Down
14 changes: 14 additions & 0 deletions lib/service/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,10 @@ type WindowsDesktopConfig struct {
PublicAddrs []utils.NetAddr
// LDAP is the LDAP connection parameters.
LDAP LDAPConfig

// Discovery configures automatic desktop discovery via LDAP.
Discovery LDAPDiscoveryConfig

// Hosts is an optional list of static Windows hosts to expose through this
// service.
Hosts []utils.NetAddr
Expand All @@ -855,6 +859,16 @@ type WindowsDesktopConfig struct {
HostLabels HostLabelRules
}

type LDAPDiscoveryConfig struct {
// BaseDN is the base DN to search for desktops.
// Use the value '*' to search from the root of the domain,
// or leave blank to disable desktop discovery.
BaseDN string `yaml:"base_dn"`
// Filters are additional LDAP filters to apply to the search.
// See: https://ldap.com/ldap-filters/
Filters []string `yaml:"filters"`
}

// HostLabelRules is a collection of rules describing how to apply labels to hosts.
type HostLabelRules []HostLabelRule

Expand Down
3 changes: 2 additions & 1 deletion lib/service/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(log *logrus.
}
},
},
LDAPConfig: desktop.LDAPConfig(cfg.WindowsDesktop.LDAP),
LDAPConfig: desktop.LDAPConfig(cfg.WindowsDesktop.LDAP),
DiscoveryBaseDN: cfg.WindowsDesktop.Discovery.BaseDN,
})
if err != nil {
return trace.Wrap(err)
Expand Down
1 change: 0 additions & 1 deletion lib/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3255,7 +3255,6 @@ func setupALPNRouter(listeners *proxyListeners, serverTLSConf *tls.Config, cfg *
MatchFunc: alpnproxy.MatchByProtocol(
alpncommon.ProtocolHTTP,
alpncommon.ProtocolHTTP2,
alpncommon.ProtocolAWSCLI,
acme.ALPNProto,
),
Handler: webWrapper.HandleConnection,
Expand Down
3 changes: 0 additions & 3 deletions lib/srv/alpnproxy/common/protocols.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ const (

// ProtocolAuth allows dialing local/remote auth service based on SNI cluster name value.
ProtocolAuth Protocol = "teleport-auth@"

// ProtocolAWSCLI allows accessing the AWS API by AWS CLI.
ProtocolAWSCLI = "teleport-aws-cli"
)

// SupportedProtocols is the list of supported ALPN protocols.
Expand Down
2 changes: 1 addition & 1 deletion lib/srv/alpnproxy/local_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func createAWSAccessProxySuite(t *testing.T, cred *credentials.Credentials) *Loc
lp, err := NewLocalProxy(LocalProxyConfig{
Listener: listener,
RemoteProxyAddr: hs.Listener.Addr().String(),
Protocol: common.ProtocolAWSCLI,
Protocol: common.ProtocolHTTP,
ParentContext: context.Background(),
InsecureSkipVerify: true,
AWSCredentials: cred,
Expand Down
14 changes: 10 additions & 4 deletions lib/srv/alpnproxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ func (p *Proxy) Serve(ctx context.Context) error {
if cerr := clientConn.Close(); cerr != nil && !utils.IsOKNetworkError(cerr) {
p.log.WithError(cerr).Warnf("Failed to close client connection.")
}
if !utils.IsOKNetworkError(err) {

if trace.IsBadParameter(err) {
p.log.Warnf("Failed to handle client connection: %v", err)
} else if !utils.IsOKNetworkError(err) {
p.log.WithError(err).Warnf("Failed to handle client connection.")
}
}
Expand Down Expand Up @@ -445,9 +448,12 @@ func (p *Proxy) getHandlerDescBaseOnClientHelloMsg(clientHelloInfo *tls.ClientHe

// getHandleDescBasedOnALPNVal returns the HandlerDesc base on ALPN field read from ClientHelloInfo message.
func (p *Proxy) getHandleDescBasedOnALPNVal(clientHelloInfo *tls.ClientHelloInfo) (*HandlerDecs, error) {
// Add the HTTP protocol as a default protocol. If the server doesn't support any of client protocol or
// clientHelloInfo.SupportedProtos list is empty the default HTTP handler will be returned.
clientProtocols := append(clientHelloInfo.SupportedProtos, string(common.ProtocolHTTP))
// Add the HTTP protocol as a default protocol. If client supported
// list is empty the default HTTP handler will be returned.
clientProtocols := clientHelloInfo.SupportedProtos
if len(clientProtocols) == 0 {
clientProtocols = []string{string(common.ProtocolHTTP)}
}

for _, v := range clientProtocols {
protocol := common.Protocol(v)
Expand Down
13 changes: 0 additions & 13 deletions lib/srv/alpnproxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,19 +305,6 @@ func TestProxyALPNProtocolsRouting(t *testing.T) {
ClientNextProtos: nil,
wantProtocolHandler: string(common.ProtocolHTTP),
},
{
name: "all client protocols are unsupported - default http handler should be called",
handlers: []HandlerDecs{
makeHandler(common.ProtocolHTTP),
makeHandler(common.ProtocolProxySSH),
},
ClientNextProtos: []string{
"unknown-protocol1",
"unknown-protocol2",
"unknown-protocol3",
},
wantProtocolHandler: string(common.ProtocolHTTP),
},
}

for _, tc := range tests {
Expand Down
2 changes: 0 additions & 2 deletions lib/srv/desktop/rdp/rdpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ func New(ctx context.Context, cfg Config) (*Client, error) {
return c, nil
}

// TODO(zmb3): allow passing a ctx or otherwise configuring a timeout here
// (if we cannot route to the host this will hang indefinitely)
func (c *Client) readClientUsername() error {
for {
msg, err := c.cfg.InputMessage()
Expand Down
13 changes: 10 additions & 3 deletions lib/srv/desktop/rdp/rdpclient/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ use std::convert::TryFrom;
use std::ffi::{CStr, CString};
use std::io::Error as IoError;
use std::io::{Cursor, Read, Write};
use std::net::TcpStream;
use std::net::{TcpStream, ToSocketAddrs};
use std::os::raw::c_char;
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use std::{mem, ptr, slice};
use std::{mem, ptr, slice, time};

#[no_mangle]
pub extern "C" fn init() {
Expand Down Expand Up @@ -116,6 +116,7 @@ pub extern "C" fn connect_rdp(
enum ConnectError {
TCP(IoError),
RDP(RdpError),
InvalidAddr(),
}

impl From<IoError> for ConnectError {
Expand All @@ -130,6 +131,8 @@ impl From<RdpError> for ConnectError {
}
}

const RDP_CONNECT_TIMEOUT: time::Duration = time::Duration::from_secs(5);

fn connect_rdp_inner(
addr: &str,
username: String,
Expand All @@ -139,7 +142,11 @@ fn connect_rdp_inner(
screen_height: u16,
) -> Result<Client, ConnectError> {
// Connect and authenticate.
let tcp = TcpStream::connect(addr)?;
let addr = addr
.to_socket_addrs()?
.next()
.ok_or(ConnectError::InvalidAddr())?;
let tcp = TcpStream::connect_timeout(&addr, RDP_CONNECT_TIMEOUT)?;
let tcp_fd = tcp.as_raw_fd() as usize;
// Domain name "." means current domain.
let domain = ".";
Expand Down
18 changes: 15 additions & 3 deletions lib/srv/desktop/windows_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ type WindowsServiceConfig struct {
HostLabelsFn func(host string) map[string]string
// LDAPConfig contains parameters for connecting to an LDAP server.
LDAPConfig
// DiscoveryBaseDN is the base DN for searching for Windows Desktops.
// Desktop discovery is disabled if this field is empty.
DiscoveryBaseDN string
// TODO(zmb3): add support for LDAP filters as defined in RFD #34
}

// LDAPConfig contains parameters for connecting to an LDAP server.
Expand Down Expand Up @@ -323,9 +327,17 @@ func NewWindowsService(cfg WindowsServiceConfig) (*WindowsService, error) {
return nil, trace.Wrap(err)
}

if err := s.startDiscoveredHostHeartbeats(); err != nil {
s.Close()
return nil, trace.Wrap(err)
// for now, the only valid base DN is '*'
// TODO(zmb3): allow further customizing the search
if s.cfg.DiscoveryBaseDN == types.Wildcard {
if err := s.startDiscoveredHostHeartbeats(); err != nil {
s.Close()
return nil, trace.Wrap(err)
}
} 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")
}

return s, nil
Expand Down
2 changes: 1 addition & 1 deletion lib/web/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ type privilegeTokenRequest struct {
// U2FSignResponse is u2f sign response for a u2f challenge.
U2FSignResponse *u2f.AuthenticateChallengeResponse `json:"u2fSignResponse"`
// WebauthnResponse is the response from authenticators.
WebauthnResponse *wanlib.CredentialAssertionResponse
WebauthnResponse *wanlib.CredentialAssertionResponse `json:"webauthnAssertionResponse"`
}

// createPrivilegeTokenHandle creates and returns a privilege token.
Expand Down
21 changes: 15 additions & 6 deletions rfd/0034-desktop-access-windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ translates the Teleport desktop protocol into RDP:
It can also talk to `localhost` RDP service, if installed on a Windows machine
in agent mode (described below).

If configured with Active Directory Domain Controller credentials,
`windows_desktop_service` also discovers all available Windows hosts from
Active Directory and registers them in Teleport as `WindowsDesktop` objects.
Without Domain Controller credentials, `windows_desktop_service` uses a static
list of Windows hosts provided in `teleport.yaml`.
`windows_desktop_service` has the ability to automatically discover available
Windows hosts from Active Directory by performing an LDAP search. In addition,
`windows_desktop_service` can use a static list of Windows hosts provided in
`teleport.yaml`.

### Supported versions

Expand Down Expand Up @@ -182,13 +181,17 @@ connect to. Internally, Teleport tracks known Windows hosts using

There are 3 ways that `windows_desktop_service` discovers Windows hosts to
register:
- hardcoded list of standalone hosts provided in the config file (see
- hardcoded list of hosts provided in the config file (see
[configuration](#configuration))
- list of Active Directory-enrolled hosts obtained from AD via LDAPS (LDAP over
SSL)
- LDAP library: https://pkg.go.dev/github.com/go-ldap/ldap/v3
- local host, when running on a Windows machine in agent mode

By default, Teleport will only register hosts that are provided in the
configuration file. To enable host discovery over LDAP, additional configuration
is necessary (see [configuration](#configuration)).

#### Automatic Host Labels

Teleport will automatically apply the following host labels to hosts which are
Expand Down Expand Up @@ -243,6 +246,12 @@ windows_desktop_service:
- win1.example.com
- win2.example.com
- ...
# (optional) settings for enabling automatic desktop discovery via LDAP
discovery:
base_dn: '*' # wildcard searches from the root, leave empty to disable discovery
filters: # additional LDAP filters: https://ldap.com/ldap-filters/
- filter1 # note: multiple filters are combined into an AND filter
- filter2
# (optional) host_labels applies labels to windows hosts for RBAC.
# Each entry maps to a subset of hosts by regexp and applies a group of labels.
# A host can match multiple regexps and will get a union of all the labels.
Expand Down
2 changes: 1 addition & 1 deletion tool/tsh/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func createLocalAWSCLIProxy(cf *CLIConf, tc *client.TeleportClient, cred *creden
lp, err := alpnproxy.NewLocalProxy(alpnproxy.LocalProxyConfig{
Listener: listener,
RemoteProxyAddr: tc.WebProxyAddr,
Protocol: alpncommon.ProtocolAWSCLI,
Protocol: alpncommon.ProtocolHTTP,
InsecureSkipVerify: cf.InsecureSkipVerify,
ParentContext: cf.Context,
SNI: address.Host(),
Expand Down
28 changes: 27 additions & 1 deletion tool/tsh/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ package main
import (
"fmt"
"net"
"os"
"text/template"

"github.com/gravitational/trace"

libclient "github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/srv/alpnproxy"
alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common"
Expand Down Expand Up @@ -92,7 +95,21 @@ func onProxyCommandDB(cf *CLIConf) error {
lp.Close()
}()

fmt.Printf("Started DB proxy on %s\n", listener.Addr())
profile, err := libclient.StatusCurrent("", cf.Proxy)
if err != nil {
return trace.Wrap(err)
}

err = dbProxyTpl.Execute(os.Stdout, map[string]string{
"database": database.ServiceName,
"address": listener.Addr().String(),
"ca": profile.CACertPath(),
"cert": profile.DatabaseCertPath(database.ServiceName),
"key": profile.KeyPath(),
})
if err != nil {
return trace.Wrap(err)
}

defer lp.Close()
if err := lp.Start(cf.Context); err != nil {
Expand Down Expand Up @@ -136,3 +153,12 @@ func toALPNProtocol(dbProtocol string) (alpncommon.Protocol, error) {
return "", trace.NotImplemented("%q protocol is not supported", dbProtocol)
}
}

// dbProxyTpl is the message that gets printed to a user when a database proxy is started.
var dbProxyTpl = template.Must(template.New("").Parse(`Started DB proxy on {{.address}}
Use following credentials to connect to the {{.database}} proxy:
ca_file={{.ca}}
cert_file={{.cert}}
key_file={{.key}}
`))

0 comments on commit c7e6323

Please sign in to comment.