Skip to content

Commit

Permalink
Backport - API Client Credential/Connection changes (#6131)
Browse files Browse the repository at this point in the history
* API client connection overhaul (#5625)

* Add Credential loader support for tsh profiles. (#5993)

* Profile credentials dialer fix (#6122)
  • Loading branch information
Joerger authored Mar 30, 2021
1 parent a0128e1 commit 26db273
Show file tree
Hide file tree
Showing 53 changed files with 1,887 additions and 1,134 deletions.
358 changes: 255 additions & 103 deletions api/client/client.go

Large diffs are not rendered by default.

50 changes: 34 additions & 16 deletions api/client/contextdialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import (
"net"
"time"

"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/utils/sshutils"

"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
)

// ContextDialer represents network dialer interface that uses context
Expand All @@ -38,23 +42,37 @@ func (f ContextDialerFunc) DialContext(ctx context.Context, network, addr string
return f(ctx, network, addr)
}

// NewAddrDialer makes a new dialer from a list of addresses
func NewAddrDialer(addrs []string, keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) {
if len(addrs) == 0 {
return nil, trace.BadParameter("no addreses to dial")
}
dialer := net.Dialer{
// NewDialer makes a new dialer.
func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer {
return &net.Dialer{
Timeout: dialTimeout,
KeepAlive: keepAliveInterval,
KeepAlive: keepAlivePeriod,
}
return ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) {
for _, addr := range addrs {
conn, err = dialer.DialContext(ctx, network, addr)
if err == nil {
return conn, nil
}
}

// NewTunnelDialer make a new ssh tunnel dialer.
func NewTunnelDialer(ssh ssh.ClientConfig, keepAlivePeriod, dialTimeout time.Duration) ContextDialer {
dialer := NewDialer(keepAlivePeriod, dialTimeout)
return ContextDialerFunc(func(ctx context.Context, network, addr string) (conn net.Conn, err error) {
conn, err = dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, trace.Wrap(err)
}

ssh.Timeout = dialTimeout
sconn, err := sshutils.NewClientConnWithDeadline(conn, addr, &ssh)
if err != nil {
return nil, trace.NewAggregate(err, conn.Close())
}

// Build a net.Conn over the tunnel. Make this an exclusive connection:
// close the net.Conn as well as the channel upon close.
conn, _, err = sshutils.ConnectProxyTransport(sconn.Conn, &sshutils.DialReq{
Address: constants.RemoteAuthServer,
}, true)
if err != nil {
return nil, trace.NewAggregate(err, sconn.Close())
}
// not wrapping on purpose to preserve the original error
return nil, err
}), nil
return conn, nil
})
}
163 changes: 146 additions & 17 deletions api/client/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,27 @@ limitations under the License.
package client

import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net"

"github.com/gravitational/teleport/api/constants"

"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
"golang.org/x/net/http2"
)

// Credentials are used to authenticate to Auth.
type Credentials interface {
// Dialer is used to dial a connection to Auth.
Dialer() (ContextDialer, error)
// Config returns TLS configuration used to connect to Auth.
Config() (*tls.Config, error)
// Dialer is used to create a dialer used to connect to Auth.
Dialer(cfg Config) (ContextDialer, error)
// TLSConfig returns TLS configuration used to connect to Auth.
TLSConfig() (*tls.Config, error)
// SSHClientConfig returns SSH configuration used to connect to Proxy through tunnel.
SSHClientConfig() (*ssh.ClientConfig, error)
}

// LoadTLS is used to load credentials directly from another *tls.Config.
Expand All @@ -48,15 +54,23 @@ type TLSConfigCreds struct {
}

// Dialer is used to dial a connection to Auth.
func (c *TLSConfigCreds) Dialer() (ContextDialer, error) {
func (c *TLSConfigCreds) Dialer(cfg Config) (ContextDialer, error) {
return nil, trace.NotImplemented("no dialer")
}

// Config returns TLS configuration used to connect to Auth.
func (c *TLSConfigCreds) Config() (*tls.Config, error) {
// TLSConfig returns TLS configuration used to connect to Auth.
func (c *TLSConfigCreds) TLSConfig() (*tls.Config, error) {
if c.tlsConfig == nil {
return nil, trace.BadParameter("tls config is nil")
}
return configure(c.tlsConfig), nil
}

// SSHClientConfig returns SSH configuration used to connect to Proxy.
func (c *TLSConfigCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
return nil, trace.NotImplemented("no ssh config")
}

// LoadKeyPair is used to load credentials from files on disk.
func LoadKeyPair(certFile string, keyFile string, caFile string) *KeyPairCreds {
return &KeyPairCreds{
Expand All @@ -75,12 +89,12 @@ type KeyPairCreds struct {
}

// Dialer is used to dial a connection to Auth.
func (c *KeyPairCreds) Dialer() (ContextDialer, error) {
func (c *KeyPairCreds) Dialer(cfg Config) (ContextDialer, error) {
return nil, trace.NotImplemented("no dialer")
}

// Config returns TLS configuration used to connect to Auth.
func (c *KeyPairCreds) Config() (*tls.Config, error) {
// TLSConfig returns TLS configuration used to connect to Auth.
func (c *KeyPairCreds) TLSConfig() (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile)
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -102,6 +116,11 @@ func (c *KeyPairCreds) Config() (*tls.Config, error) {
}), nil
}

// SSHClientConfig returns SSH configuration used to connect to Proxy.
func (c *KeyPairCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
return nil, trace.NotImplemented("no ssh config")
}

// LoadIdentityFile is used to load credentials from an identity file on disk.
func LoadIdentityFile(path string) *IdentityCreds {
return &IdentityCreds{
Expand All @@ -112,29 +131,139 @@ func LoadIdentityFile(path string) *IdentityCreds {
// IdentityCreds are used to authenticate the client
// with an identity file generated in the given file path.
type IdentityCreds struct {
path string
path string
identityFile *IdentityFile
}

// Dialer is used to dial a connection to Auth.
func (c *IdentityCreds) Dialer() (ContextDialer, error) {
func (c *IdentityCreds) Dialer(cfg Config) (ContextDialer, error) {
return nil, trace.NotImplemented("no dialer")
}

// Config returns TLS configuration used to connect to Auth.
func (c *IdentityCreds) Config() (*tls.Config, error) {
identityFile, err := ReadIdentityFile(c.path)
// TLSConfig returns TLS configuration used to connect to Auth.
func (c *IdentityCreds) TLSConfig() (*tls.Config, error) {
if err := c.load(); err != nil {
return nil, trace.Wrap(err)
}

tlsConfig, err := c.identityFile.TLSConfig()
if err != nil {
return nil, trace.Wrap(err)
}

return configure(tlsConfig), nil
}

// SSHClientConfig returns SSH configuration used to connect to Proxy.
func (c *IdentityCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
if err := c.load(); err != nil {
return nil, trace.Wrap(err)
}

sshConfig, err := c.identityFile.SSHClientConfig()
if err != nil {
return nil, trace.Wrap(err)
}

return sshConfig, nil
}

// load is used to lazy load the identity file from persistent storage.
// This allows LoadIdentity to avoid possible errors for UX purposes.
func (c *IdentityCreds) load() error {
if c.identityFile != nil {
return nil
}
var err error
if c.identityFile, err = ReadIdentityFile(c.path); err != nil {
return trace.BadParameter("identity file could not be decoded: %v", err)
}
return nil
}

// LoadProfile is used to load credentials from a tsh Profile.
// If dir is not specified, the default profile path will be used.
// If name is not specified, the current profile name will be used.
func LoadProfile(dir, name string) *ProfileCreds {
return &ProfileCreds{
dir: dir,
name: name,
}
}

// ProfileCreds are used to authenticate the client
// with a tsh profile with the given directory and name.
type ProfileCreds struct {
dir string
name string
profile *Profile
}

// Dialer is used to dial a connection to Auth.
func (c *ProfileCreds) Dialer(cfg Config) (ContextDialer, error) {
sshConfig, err := c.SSHClientConfig()
if err != nil {
return nil, trace.BadParameter("identity file could not be decoded: %v", err)
return nil, trace.Wrap(err)
}

tlsConfig, err := identityFile.TLS()
dialer := NewTunnelDialer(*sshConfig, cfg.KeepAlivePeriod, cfg.DialTimeout)
return ContextDialerFunc(func(ctx context.Context, network, _ string) (conn net.Conn, err error) {
// Ping web proxy to retrieve tunnel proxy address.
pr, err := Find(ctx, c.profile.WebProxyAddr, cfg.InsecureAddressDiscovery, nil)
if err != nil {
return nil, trace.Wrap(err)
}

conn, err = dialer.DialContext(ctx, network, pr.Proxy.SSH.TunnelPublicAddr)
if err != nil {
// not wrapping on purpose to preserve the original error
return nil, err
}
return conn, nil
}), nil
}

// TLSConfig returns TLS configuration used to connect to Auth.
func (c *ProfileCreds) TLSConfig() (*tls.Config, error) {
if err := c.load(); err != nil {
return nil, trace.Wrap(err)
}

tlsConfig, err := c.profile.TLSConfig()
if err != nil {
return nil, trace.Wrap(err)
}

return configure(tlsConfig), nil
}

// SSHClientConfig returns SSH configuration used to connect to Proxy.
func (c *ProfileCreds) SSHClientConfig() (*ssh.ClientConfig, error) {
if err := c.load(); err != nil {
return nil, trace.Wrap(err)
}

sshConfig, err := c.profile.SSHClientConfig()
if err != nil {
return nil, trace.Wrap(err)
}

return sshConfig, nil
}

// load is used to lazy load the profile from persistent storage.
// This allows LoadProfile to avoid possible errors for UX purposes.
func (c *ProfileCreds) load() error {
if c.profile != nil {
return nil
}
var err error
if c.profile, err = ProfileFromDir(c.dir, c.name); err != nil {
return trace.BadParameter("profile could not be decoded: %v", err)
}
return nil
}

func configure(c *tls.Config) *tls.Config {
tlsConfig := c.Clone()

Expand Down
Loading

0 comments on commit 26db273

Please sign in to comment.