From 40d31f22d1c1897a97b471bff4b4f3f84ee72157 Mon Sep 17 00:00:00 2001 From: Brian Joerger Date: Wed, 17 Mar 2021 12:29:20 -0700 Subject: [PATCH] Add Credential loader support for tsh profiles. --- api/client/client.go | 10 +- api/client/contextdialer.go | 10 +- api/client/credentials.go | 98 +++++++++++++++++- api/client/credentials_test.go | 115 +++++++++++++++++---- {lib => api}/client/profile.go | 155 +++++++++++++++++++++------- {lib => api}/client/profile_test.go | 3 +- api/constants/constants.go | 17 ++- api/go.mod | 2 + api/go.sum | 3 + api/sshutils/ssh.go | 4 +- integration/integration_test.go | 3 +- lib/benchmark/benchmark.go | 3 +- lib/client/api.go | 33 +++--- lib/client/keyagent_test.go | 2 +- lib/client/keystore.go | 79 ++++++-------- lib/web/sessions.go | 6 +- tool/tsh/tsh_test.go | 7 +- 17 files changed, 405 insertions(+), 145 deletions(-) rename {lib => api}/client/profile.go (50%) rename {lib => api}/client/profile_test.go (97%) diff --git a/api/client/client.go b/api/client/client.go index a0051aee922d1..27c6cdb6a8349 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -200,7 +200,11 @@ func connect(ctx context.Context, cfg Config) (*Client, error) { } // Connect with dialer provided in creds. - if dialer, err := creds.Dialer(); err == nil { + if dialer, err := creds.Dialer(cfg.KeepAlivePeriod, cfg.DialTimeout); err != nil { + if !trace.IsNotImplemented(err) { + sendError(trace.Wrap(err)) + } + } else { syncConnect(constants.APIDomain, &Client{ c: cfg, tlsConfig: tlsConfig, @@ -255,6 +259,10 @@ func connect(ctx context.Context, cfg Config) (*Client, error) { } // errChan is closed, return errors. if len(errs) == 0 { + if len(cfg.Addrs) == 0 && cfg.Dialer == nil { + // Some credentials don't require these fields. If no errors propogate, then they need to provide these fields. + return nil, trace.Errorf("all auth methods failed: try providing Addrs or Dialer in config") + } return nil, trace.Errorf("all auth methods failed") } return nil, trace.Wrap(trace.NewAggregate(errs...), "all auth methods failed") diff --git a/api/client/contextdialer.go b/api/client/contextdialer.go index 1f4c0a7e2af80..dec60bc4884fb 100644 --- a/api/client/contextdialer.go +++ b/api/client/contextdialer.go @@ -43,16 +43,16 @@ func (f ContextDialerFunc) DialContext(ctx context.Context, network, addr string } // NewDialer makes a new dialer. -func NewDialer(keepAliveInterval, dialTimeout time.Duration) ContextDialer { +func NewDialer(keepAlivePeriod, dialTimeout time.Duration) ContextDialer { return &net.Dialer{ Timeout: dialTimeout, - KeepAlive: keepAliveInterval, + KeepAlive: keepAlivePeriod, } } -// NewTunnelDialer make a new ssh tunnel dialer -func NewTunnelDialer(ssh ssh.ClientConfig, keepAliveInterval, dialTimeout time.Duration) ContextDialer { - dialer := NewDialer(keepAliveInterval, dialTimeout) +// 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 { diff --git a/api/client/credentials.go b/api/client/credentials.go index 5e1ed4ac5e037..629381ba0bcf8 100644 --- a/api/client/credentials.go +++ b/api/client/credentials.go @@ -17,9 +17,12 @@ limitations under the License. package client import ( + "context" "crypto/tls" "crypto/x509" "io/ioutil" + "net" + "time" "github.com/gravitational/teleport/api/constants" @@ -30,8 +33,8 @@ import ( // Credentials are used to authenticate to Auth. type Credentials interface { - // Dialer is used to dial a connection to Auth. - Dialer() (ContextDialer, error) + // Dialer is used to create a dialer used to connect to Auth. + Dialer(keepAliveInterval, dialTimeout time.Duration) (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. @@ -52,7 +55,7 @@ type TLSConfigCreds struct { } // Dialer is used to dial a connection to Auth. -func (c *TLSConfigCreds) Dialer() (ContextDialer, error) { +func (c *TLSConfigCreds) Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) { return nil, trace.NotImplemented("no dialer") } @@ -87,7 +90,7 @@ type KeyPairCreds struct { } // Dialer is used to dial a connection to Auth. -func (c *KeyPairCreds) Dialer() (ContextDialer, error) { +func (c *KeyPairCreds) Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) { return nil, trace.NotImplemented("no dialer") } @@ -134,7 +137,7 @@ type IdentityCreds struct { } // Dialer is used to dial a connection to Auth. -func (c *IdentityCreds) Dialer() (ContextDialer, error) { +func (c *IdentityCreds) Dialer(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) { return nil, trace.NotImplemented("no dialer") } @@ -166,6 +169,8 @@ func (c *IdentityCreds) SSHClientConfig() (*ssh.ClientConfig, error) { 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 @@ -177,6 +182,89 @@ func (c *IdentityCreds) load() error { 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(keepAliveInterval, dialTimeout time.Duration) (ContextDialer, error) { + sshConfig, err := c.SSHClientConfig() + if err != nil { + return nil, trace.Wrap(err) + } + + dialer := NewTunnelDialer(*sshConfig, keepAliveInterval, 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, false, 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() diff --git a/api/client/credentials_test.go b/api/client/credentials_test.go index 0b420f0fb4652..2ea61bdac4683 100644 --- a/api/client/credentials_test.go +++ b/api/client/credentials_test.go @@ -20,9 +20,13 @@ import ( "crypto/tls" "crypto/x509" "io/ioutil" + "os" "path/filepath" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/stretchr/testify/require" @@ -34,15 +38,12 @@ func TestLoadTLS(t *testing.T) { // Load expected tls.Config. expectedTLSConfig := getExpectedTLSConfig(t) - // Load TLSConfigCreds. creds := LoadTLS(expectedTLSConfig) - // Build tls.Config and compare to expected tls.Config. tlsConfig, err := creds.TLSConfig() require.NoError(t, err) - require.Equal(t, expectedTLSConfig.Certificates, tlsConfig.Certificates) - require.Equal(t, expectedTLSConfig.RootCAs.Subjects(), tlsConfig.RootCAs.Subjects()) + requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) // Load invalid tls.Config. invalidTLSCreds := LoadTLS(nil) @@ -64,11 +65,11 @@ func TestLoadIdentityFile(t *testing.T) { idFile := &IdentityFile{ PrivateKey: keyPEM, Certs: Certs{ - TLS: certPEM, + TLS: tlsCert, SSH: sshCert, }, CACerts: CACerts{ - TLS: [][]byte{caCertPEM}, + TLS: [][]byte{tlsCACert}, SSH: [][]byte{sshCACert}, }, } @@ -77,17 +78,15 @@ func TestLoadIdentityFile(t *testing.T) { // Load identity file from disk. creds := LoadIdentityFile(path) - // Build tls.Config and compare to expected tls.Config. tlsConfig, err := creds.TLSConfig() require.NoError(t, err) - require.Equal(t, expectedTLSConfig.Certificates, tlsConfig.Certificates) - require.Equal(t, expectedTLSConfig.RootCAs.Subjects(), tlsConfig.RootCAs.Subjects()) + requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) // Build ssh.ClientConfig and compare to expected ssh.ClientConfig. sshConfig, err := creds.SSHClientConfig() require.NoError(t, err) - require.Equal(t, expectedSSHConfig.User, sshConfig.User) + requireEqualSSHConfig(t, expectedSSHConfig, sshConfig) // Load invalid identity. creds = LoadIdentityFile("invalid_path") @@ -106,21 +105,19 @@ func TestLoadKeyPair(t *testing.T) { // Write key pair and CAs files from bytes. path := t.TempDir() + "username" certPath, keyPath, caPath := path+".crt", path+".key", path+".cas" - err := ioutil.WriteFile(certPath, certPEM, 0600) + err := ioutil.WriteFile(certPath, tlsCert, 0600) require.NoError(t, err) err = ioutil.WriteFile(keyPath, keyPEM, 0600) require.NoError(t, err) - err = ioutil.WriteFile(caPath, caCertPEM, 0600) + err = ioutil.WriteFile(caPath, tlsCACert, 0600) require.NoError(t, err) // Load key pair from disk. creds := LoadKeyPair(certPath, keyPath, caPath) - // Build tls.Config and compare to expected tls.Config. tlsConfig, err := creds.TLSConfig() require.NoError(t, err) - require.Equal(t, expectedTLSConfig.Certificates, tlsConfig.Certificates) - require.Equal(t, expectedTLSConfig.RootCAs.Subjects(), tlsConfig.RootCAs.Subjects()) + requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) // Load invalid keypairs. invalidIdentityCreds := LoadKeyPair("invalid_path", "invalid_path", "invalid_path") @@ -128,12 +125,79 @@ func TestLoadKeyPair(t *testing.T) { require.Error(t, err) } +func TestLoadProfile(t *testing.T) { + t.Parallel() + + // Load expected tls.Config and ssh.ClientConfig. + expectedTLSConfig := getExpectedTLSConfig(t) + expectedSSHConfig := getExpectedSSHConfig(t) + + // Write identity file to disk. + dir := t.TempDir() + name := "proxy" + p := &Profile{ + WebProxyAddr: "proxy:3080", + Username: "testUser", + Dir: dir, + } + + // Save profile to a file. + err := p.SaveToDir(dir, true) + require.NoError(t, err) + + // Write keys to disk. + keyDir := filepath.Join(dir, constants.SessionKeyDir) + err = os.MkdirAll(keyDir, 0700) + require.NoError(t, err) + userKeyDir := filepath.Join(keyDir, p.Name()) + os.MkdirAll(userKeyDir, 0700) + require.NoError(t, err) + keyPath := filepath.Join(userKeyDir, p.Username) + err = ioutil.WriteFile(keyPath, []byte(keyPEM), 0600) + require.NoError(t, err) + tlsCertPath := filepath.Join(userKeyDir, p.Username+constants.FileExtTLSCert) + err = ioutil.WriteFile(tlsCertPath, []byte(tlsCert), 0600) + require.NoError(t, err) + tlsCasPath := filepath.Join(userKeyDir, constants.FileNameTLSCerts) + err = ioutil.WriteFile(tlsCasPath, []byte(tlsCACert), 0600) + require.NoError(t, err) + sshCertPath := filepath.Join(userKeyDir, p.Username+constants.FileExtSSHCert) + err = ioutil.WriteFile(sshCertPath, []byte(sshCert), 0600) + require.NoError(t, err) + sshCasPath := filepath.Join(dir, constants.FileNameKnownHosts) + err = ioutil.WriteFile(sshCasPath, []byte(sshCACert), 0600) + require.NoError(t, err) + + // Load profile from disk. + creds := LoadProfile(dir, name) + // Build tls.Config and compare to expected tls.Config. + tlsConfig, err := creds.TLSConfig() + require.NoError(t, err) + requireEqualTLSConfig(t, expectedTLSConfig, tlsConfig) + // Build ssh.ClientConfig and compare to expected ssh.ClientConfig. + sshConfig, err := creds.SSHClientConfig() + require.NoError(t, err) + requireEqualSSHConfig(t, expectedSSHConfig, sshConfig) + // Build Dialer + _, err = creds.Dialer(0, 0) + require.NoError(t, err) + + // Load invalid profile. + creds = LoadProfile("invalid_dir", "invalid_name") + _, err = creds.TLSConfig() + require.Error(t, err) + _, err = creds.SSHClientConfig() + require.Error(t, err) + _, err = creds.Dialer(0, 0) + require.Error(t, err) +} + func getExpectedTLSConfig(t *testing.T) *tls.Config { - cert, err := tls.X509KeyPair(certPEM, keyPEM) + cert, err := tls.X509KeyPair(tlsCert, keyPEM) require.NoError(t, err) pool := x509.NewCertPool() - require.True(t, pool.AppendCertsFromPEM(caCertPEM)) + require.True(t, pool.AppendCertsFromPEM(tlsCACert)) return configure(&tls.Config{ Certificates: []tls.Certificate{cert}, @@ -148,8 +212,21 @@ func getExpectedSSHConfig(t *testing.T) *ssh.ClientConfig { return config } +func requireEqualTLSConfig(t *testing.T, expected *tls.Config, actual *tls.Config) { + require.Empty(t, cmp.Diff(expected, actual, + cmpopts.IgnoreFields(tls.Config{}, "GetClientCertificate"), + cmpopts.IgnoreUnexported(tls.Config{}, x509.CertPool{}), + )) +} + +func requireEqualSSHConfig(t *testing.T, expected *ssh.ClientConfig, actual *ssh.ClientConfig) { + require.Empty(t, cmp.Diff(expected, actual, + cmpopts.IgnoreFields(ssh.ClientConfig{}, "Auth", "HostKeyCallback"), + )) +} + var ( - certPEM = []byte(`-----BEGIN CERTIFICATE----- + tlsCert = []byte(`-----BEGIN CERTIFICATE----- MIIDyzCCArOgAwIBAgIQD3MiJ2Au8PicJpCNFbvcETANBgkqhkiG9w0BAQsFADBe MRQwEgYDVQQKEwtleGFtcGxlLmNvbTEUMBIGA1UEAxMLZXhhbXBsZS5jb20xMDAu BgNVBAUTJzIwNTIxNzE3NzMzMTIxNzQ2ODMyNjA5NjAxODEwODc0NTAzMjg1ODAe @@ -201,7 +278,7 @@ pr5VAoGBAJBhNjs9wAu+ZoPcMZcjIXT/BAj2tQYiHoRnNpvQjDYbQueUBeI0Ry8d 90Ns/9SamlBo9j8ETm9g9D3EVir9zF5XvoR13OdN9gabGy1GuubT -----END RSA PRIVATE KEY-----`) - caCertPEM = []byte(`-----BEGIN CERTIFICATE----- + tlsCACert = []byte(`-----BEGIN CERTIFICATE----- MIIDiTCCAnGgAwIBAgIRAJlp/39yg8U604bjsxgcoC0wDQYJKoZIhvcNAQELBQAw XjEUMBIGA1UEChMLZXhhbXBsZS5jb20xFDASBgNVBAMTC2V4YW1wbGUuY29tMTAw LgYDVQQFEycyMDM5MjIyNTY2MzcxMDQ0NDc3MzYxNjA0MTk0NjU2MTgzMDA5NzMw diff --git a/lib/client/profile.go b/api/client/profile.go similarity index 50% rename from lib/client/profile.go rename to api/client/profile.go index 89a75291de822..f32e38539818b 100644 --- a/lib/client/profile.go +++ b/api/client/profile.go @@ -1,6 +1,24 @@ +/* +Copyright 2021 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package client import ( + "crypto/tls" + "crypto/x509" "io/ioutil" "net" "os" @@ -8,21 +26,21 @@ import ( "path/filepath" "strings" - "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/sshutils" + "github.com/gravitational/trace" + "golang.org/x/crypto/ssh" "gopkg.in/yaml.v2" ) -// CurrentProfileSymlink is a filename which is a symlink to the -// current profile, usually something like this: -// -// ~/.tsh/profile -> ~/.tsh/staging.yaml -// -const CurrentProfileSymlink = "profile" - -// CurrentProfileFilename is a file which stores the name of the -// currently active profile. -const CurrentProfileFilename = "current-profile" +const ( + // profileDir is the default root directory where tsh stores profiles. + profileDir = ".tsh" + // currentProfileFilename is a file which stores the name of the + // currently active profile. + currentProfileFilename = "current-profile" +) // Profile is a collection of most frequently used CLI flags // for "tsh". @@ -58,25 +76,80 @@ type Profile struct { // DynamicForwardedPorts is a list of ports to use for dynamic port // forwarding (SOCKS5). DynamicForwardedPorts []string `yaml:"dynamic_forward_ports,omitempty"` + + // Dir is the directory of this profile. + Dir string } // Name returns the name of the profile. -func (cp *Profile) Name() string { - addr, _, err := net.SplitHostPort(cp.WebProxyAddr) +func (p *Profile) Name() string { + addr, _, err := net.SplitHostPort(p.WebProxyAddr) if err != nil { - return cp.WebProxyAddr + return p.WebProxyAddr } return addr } +// TLSConfig returns the profile's associated TLSConfig. +func (p *Profile) TLSConfig() (*tls.Config, error) { + credsPath := filepath.Join(p.Dir, constants.SessionKeyDir, p.Name()) + + certPath := filepath.Join(credsPath, p.Username+constants.FileExtTLSCert) + keyPath := filepath.Join(credsPath, p.Username) + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, trace.Wrap(err) + } + + certsPath := filepath.Join(credsPath, constants.FileNameTLSCerts) + certs, err := ioutil.ReadFile(certsPath) + if err != nil { + return nil, trace.Wrap(err) + } + + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(certs) { + return nil, trace.BadParameter("invalid CA cert PEM") + } + + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: pool, + }, nil +} + +// SSHClientConfig returns the profile's associated SSHClientConfig. +func (p *Profile) SSHClientConfig() (*ssh.ClientConfig, error) { + credsPath := filepath.Join(p.Dir, constants.SessionKeyDir, p.Name()) + cert, err := ioutil.ReadFile(filepath.Join(credsPath, p.Username+constants.FileExtSSHCert)) + if err != nil { + return nil, trace.Wrap(err) + } + key, err := ioutil.ReadFile(filepath.Join(credsPath, p.Username)) + if err != nil { + return nil, trace.Wrap(err) + } + + knownHosts, err := ioutil.ReadFile(filepath.Join(p.Dir, constants.FileNameKnownHosts)) + if err != nil { + return nil, trace.Wrap(err) + } + + ssh, err := sshutils.SSHClientConfig(cert, key, [][]byte{knownHosts}) + if err != nil { + return nil, trace.Wrap(err) + } + return ssh, nil +} + // SetCurrentProfileName attempts to set the current profile name. func SetCurrentProfileName(dir string, name string) error { if dir == "" { return trace.BadParameter("cannot set current profile: missing dir") } - path := filepath.Join(dir, CurrentProfileFilename) + path := filepath.Join(dir, currentProfileFilename) if err := ioutil.WriteFile(path, []byte(strings.TrimSpace(name)+"\n"), 0660); err != nil { return trace.Wrap(err) } @@ -89,7 +162,7 @@ func GetCurrentProfileName(dir string) (name string, err error) { return "", trace.BadParameter("cannot get current profile: missing dir") } - data, err := ioutil.ReadFile(filepath.Join(dir, CurrentProfileFilename)) + data, err := ioutil.ReadFile(filepath.Join(dir, currentProfileFilename)) if err != nil { if os.IsNotExist(err) { return "", trace.NotFound("current-profile is not set") @@ -136,22 +209,24 @@ func FullProfilePath(dir string) string { if dir != "" { return dir } - // get user home dir: + return defaultProfilePath() +} + +// defaultProfilePath retrieves the default path of the TSH profile. +func defaultProfilePath() string { home := os.TempDir() - u, err := user.Current() - if err == nil { + if u, err := user.Current(); err == nil { home = u.HomeDir } - return filepath.Join(home, ProfileDir) + return filepath.Join(home, profileDir) } // ProfileFromDir reads the user (yaml) profile from a given directory. If -// name is empty, this function defaults to loading the currently active +// dir is empty, this function defaults to the default tsh profile directory. +// If name is empty, this function defaults to loading the currently active // profile (if any). func ProfileFromDir(dir string, name string) (*Profile, error) { - if dir == "" { - return nil, trace.BadParameter("cannot load profile: missing dir") - } + dir = FullProfilePath(dir) var err error if name == "" { name, err = GetCurrentProfileName(dir) @@ -159,39 +234,45 @@ func ProfileFromDir(dir string, name string) (*Profile, error) { return nil, trace.Wrap(err) } } - - return ProfileFromFile(filepath.Join(dir, name+".yaml")) + p, err := profileFromFile(filepath.Join(dir, name+".yaml")) + if err != nil { + return nil, trace.Wrap(err) + } + return p, nil } -// ProfileFromFile loads the profile from a YAML file -func ProfileFromFile(filePath string) (*Profile, error) { +// profileFromFile loads the profile from a YAML file. +func profileFromFile(filePath string) (*Profile, error) { bytes, err := ioutil.ReadFile(filePath) if err != nil { return nil, trace.ConvertSystemError(err) } - var cp *Profile - if err := yaml.Unmarshal(bytes, &cp); err != nil { + var p *Profile + if err := yaml.Unmarshal(bytes, &p); err != nil { return nil, trace.Wrap(err) } - return cp, nil + p.Dir = filepath.Dir(filePath) + return p, nil } -func (cp *Profile) SaveToDir(dir string, makeCurrent bool) error { +// SaveToDir saves this profile to the specified directory. +// If makeCurrent is true, it makes this profile current. +func (p *Profile) SaveToDir(dir string, makeCurrent bool) error { if dir == "" { return trace.BadParameter("cannot save profile: missing dir") } - if err := cp.SaveToFile(filepath.Join(dir, cp.Name()+".yaml")); err != nil { + if err := p.saveToFile(filepath.Join(dir, p.Name()+".yaml")); err != nil { return trace.Wrap(err) } if makeCurrent { - return trace.Wrap(SetCurrentProfileName(dir, cp.Name())) + return trace.Wrap(SetCurrentProfileName(dir, p.Name())) } return nil } -// SaveToFile saves Profile to the target file. -func (cp *Profile) SaveToFile(filepath string) error { - bytes, err := yaml.Marshal(&cp) +// saveToFile saves this profile to the specified file. +func (p *Profile) saveToFile(filepath string) error { + bytes, err := yaml.Marshal(&p) if err != nil { return trace.Wrap(err) } diff --git a/lib/client/profile_test.go b/api/client/profile_test.go similarity index 97% rename from lib/client/profile_test.go rename to api/client/profile_test.go index 0f5db8c0f6c3d..7a9f5fac44e0e 100644 --- a/lib/client/profile_test.go +++ b/api/client/profile_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016-2019 Gravitational, Inc. +Copyright 2016-2021 Gravitational, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ func TestProfileBasics(t *testing.T) { Username: "testuser", ForwardedPorts: []string{"8000:example.com:8000"}, DynamicForwardedPorts: []string{"localhost:8080"}, + Dir: dir, } // verify that profile name is proxy host component diff --git a/api/constants/constants.go b/api/constants/constants.go index 8c161db1051b9..c62a2afd0027b 100644 --- a/api/constants/constants.go +++ b/api/constants/constants.go @@ -124,7 +124,22 @@ const ( // RemoteAuthServer is a special non-resolvable address that indicates client // requests a connection to the remote auth server. RemoteAuthServer = "@remote-auth-server" +) - // FileExtSSHCert is a file extension used for Certificate files +const ( + // SessionKeyDir is the sub-directory where session keys are stored (.tsh/keys). + SessionKeyDir = "keys" + // FileNameKnownHosts is a file that stores known hosts. + FileNameKnownHosts = "known_hosts" + // FileExtTLSCert is the filename extension/suffix of TLS certs + // stored in a profile (./tsh/keys/profilename/username-x509.pem). + FileExtTLSCert = "-x509.pem" + // FileNameTLSCerts is the filename of Cert Authorities stored in a + // profile (./tsh/keys/profilename/certs.pem). + FileNameTLSCerts = "certs.pem" + // FileExtCert is a file extension used for SSH Certificate files. FileExtSSHCert = "-cert.pub" + // FileExtPub is a file extension used for SSH Certificate Authorities + // stored in a profile (./tsh/keys/profilename/username.pub). + FileExtPub = ".pub" ) diff --git a/api/go.mod b/api/go.mod index 728d7efd93334..5cd531b7a3c7a 100644 --- a/api/go.mod +++ b/api/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( github.com/gogo/protobuf v1.3.1 github.com/golang/protobuf v1.4.2 + github.com/google/go-cmp v0.4.0 github.com/gravitational/trace v1.1.13 github.com/jonboulle/clockwork v0.2.2 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect @@ -14,4 +15,5 @@ require ( golang.org/x/net v0.0.0-20200707034311-ab3426394381 google.golang.org/grpc v1.27.0 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/api/go.sum b/api/go.sum index 8e618a6066870..12b13b9faa0e8 100644 --- a/api/go.sum +++ b/api/go.sum @@ -94,7 +94,10 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/api/sshutils/ssh.go b/api/sshutils/ssh.go index 185bd22d8b52d..a2e6de76f7871 100644 --- a/api/sshutils/ssh.go +++ b/api/sshutils/ssh.go @@ -57,12 +57,12 @@ func SSHClientConfig(sshCert, privKey []byte, caCerts [][]byte) (*ssh.ClientConf authMethod, err := AsAuthMethod(cert, privKey) if err != nil { - return nil, trace.Wrap(err, "failed to convert identity file to auth method") + return nil, trace.Wrap(err, "failed to convert key pair to auth method") } hostKeyCallback, err := HostKeyCallback(caCerts) if err != nil { - return nil, trace.Wrap(err, "failed to convert identity file to HostKeyCallback") + return nil, trace.Wrap(err, "failed to convert certificate authorities to HostKeyCallback") } return &ssh.ClientConfig{ diff --git a/integration/integration_test.go b/integration/integration_test.go index fd40cbf468bcd..613f1219dd105 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -44,6 +44,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" + apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/lib" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/testauthority" @@ -140,7 +141,7 @@ func (s *IntSuite) TearDownSuite(c *check.C) { } func (s *IntSuite) SetUpTest(c *check.C) { - os.RemoveAll(client.FullProfilePath("")) + os.RemoveAll(apiclient.FullProfilePath("")) } // setUpTest configures the specific test identified with the given c. diff --git a/lib/benchmark/benchmark.go b/lib/benchmark/benchmark.go index 70c7700212fa8..afccdee211a52 100644 --- a/lib/benchmark/benchmark.go +++ b/lib/benchmark/benchmark.go @@ -29,6 +29,7 @@ import ( "syscall" "time" + apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/utils" @@ -271,7 +272,7 @@ func execute(m benchMeasure) error { // makeTeleportClient creates an instance of a teleport client func makeTeleportClient(host, login, proxy string) (*client.TeleportClient, error) { c := client.Config{Host: host} - path := client.FullProfilePath("") + path := apiclient.FullProfilePath("") if login != "" { c.HostLogin = login c.Username = login diff --git a/lib/client/api.go b/lib/client/api.go index ba3d40b84dcd2..3c008b16e58b5 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -69,8 +69,6 @@ import ( ) const ( - // ProfileDir is a directory location where tsh profiles (and session keys) are stored - ProfileDir = ".tsh" AddKeysToAgentAuto = "auto" AddKeysToAgentNo = "no" AddKeysToAgentYes = "yes" @@ -390,14 +388,14 @@ func (p *ProfileStatus) IsExpired(clock clockwork.Clock) bool { // // It's stored in ~/.tsh/keys//certs.pem by default. func (p *ProfileStatus) CACertPath() string { - return filepath.Join(p.Dir, sessionKeyDir, p.Name, fileNameTLSCerts) + return filepath.Join(p.Dir, constants.SessionKeyDir, p.Name, constants.FileNameTLSCerts) } // KeyPath returns path to the private key for this profile. // // It's kept in ~/.tsh/keys//. func (p *ProfileStatus) KeyPath() string { - return filepath.Join(p.Dir, sessionKeyDir, p.Name, p.Username) + return filepath.Join(p.Dir, constants.SessionKeyDir, p.Name, p.Username) } // DatabaseCertPath returns path to the specified database access certificate @@ -405,10 +403,10 @@ func (p *ProfileStatus) KeyPath() string { // // It's kept in ~/.tsh/keys//-db//-x509.pem func (p *ProfileStatus) DatabaseCertPath(name string) string { - return filepath.Join(p.Dir, sessionKeyDir, p.Name, + return filepath.Join(p.Dir, constants.SessionKeyDir, p.Name, fmt.Sprintf("%v%v", p.Username, dbDirSuffix), p.Cluster, - fmt.Sprintf("%v%v", name, fileExtTLSCert)) + fmt.Sprintf("%v%v", name, constants.FileExtTLSCert)) } // AppCertPath returns path to the specified app access certificate @@ -416,10 +414,10 @@ func (p *ProfileStatus) DatabaseCertPath(name string) string { // // It's kept in ~/.tsh/keys//-app//-x509.pem func (p *ProfileStatus) AppCertPath(name string) string { - return filepath.Join(p.Dir, sessionKeyDir, p.Name, + return filepath.Join(p.Dir, constants.SessionKeyDir, p.Name, fmt.Sprintf("%v%v", p.Username, appDirSuffix), p.Cluster, - fmt.Sprintf("%v%v", name, fileExtTLSCert)) + fmt.Sprintf("%v%v", name, constants.FileExtTLSCert)) } // DatabaseServices returns a list of database service names for this profile. @@ -488,8 +486,12 @@ func RetryWithRelogin(ctx context.Context, tc *TeleportClient, fn func() error) func readProfile(profileDir string, profileName string) (*ProfileStatus, error) { var err error + if profileDir == "" { + return nil, trace.BadParameter("profileDir cannot be empty") + } + // Read in the profile for this proxy. - profile, err := ProfileFromDir(profileDir, profileName) + profile, err := client.ProfileFromDir(profileDir, profileName) if err != nil { return nil, trace.Wrap(err) } @@ -677,7 +679,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err } // Construct the full path to the profile requested and make sure it exists. - profileDir = FullProfilePath(profileDir) + profileDir = client.FullProfilePath(profileDir) stat, err := os.Stat(profileDir) if err != nil { log.Debugf("Failed to stat file: %v.", err) @@ -697,7 +699,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err // no proxyHost was supplied. profileName := proxyHost if profileName == "" { - profileName, err = GetCurrentProfileName(profileDir) + profileName, err = client.GetCurrentProfileName(profileDir) if err != nil { if trace.IsNotFound(err) { return nil, nil, trace.NotFound("not logged in") @@ -720,7 +722,7 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err } // load the rest of the profiles - profiles, err := ListProfileNames(profileDir) + profiles, err := client.ListProfileNames(profileDir) if err != nil { return nil, nil, trace.Wrap(err) } @@ -748,9 +750,8 @@ func Status(profileDir, proxyHost string) (*ProfileStatus, []*ProfileStatus, err // profiles directory. If profileDir is an empty string, the default profile // directory ~/.tsh is used. func (c *Config) LoadProfile(profileDir string, proxyName string) error { - profileDir = FullProfilePath(profileDir) // read the profile: - cp, err := ProfileFromDir(profileDir, ProxyHost(proxyName)) + cp, err := client.ProfileFromDir(profileDir, ProxyHost(proxyName)) if err != nil { if trace.IsNotFound(err) { return nil @@ -785,9 +786,9 @@ func (c *Config) SaveProfile(dir string, makeCurrent bool) error { return nil } - dir = FullProfilePath(dir) + dir = client.FullProfilePath(dir) - var cp Profile + var cp client.Profile cp.Username = c.Username cp.WebProxyAddr = c.WebProxyAddr cp.SSHProxyAddr = c.SSHProxyAddr diff --git a/lib/client/keyagent_test.go b/lib/client/keyagent_test.go index 7dcdf5fb8935f..4f7b1bfd71af5 100644 --- a/lib/client/keyagent_test.go +++ b/lib/client/keyagent_test.go @@ -108,7 +108,7 @@ func (s *KeyAgentTestSuite) TestAddKey(c *check.C) { c.Assert(err, check.IsNil) // check that the key has been written to disk - for _, ext := range []string{constants.FileExtSSHCert, "", fileExtPub} { + for _, ext := range []string{constants.FileExtSSHCert, "", constants.FileExtPub} { _, err := os.Stat(fmt.Sprintf("%v/keys/%v/%v%v", s.keyDir, s.hostname, s.username, ext)) c.Assert(err, check.IsNil) } diff --git a/lib/client/keystore.go b/lib/client/keystore.go index d065573c736e4..bcea9d140aeb4 100644 --- a/lib/client/keystore.go +++ b/lib/client/keystore.go @@ -24,13 +24,13 @@ import ( "io" "io/ioutil" "os" - "os/user" "path/filepath" "strings" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/sshutils" @@ -42,15 +42,9 @@ import ( ) const ( - defaultKeyDir = ProfileDir - fileExtTLSCert = "-x509.pem" - fileExtPub = ".pub" - sessionKeyDir = "keys" - fileNameKnownHosts = "known_hosts" - fileNameTLSCerts = "certs.pem" - kubeDirSuffix = "-kube" - dbDirSuffix = "-db" - appDirSuffix = "-app" + kubeDirSuffix = "-kube" + dbDirSuffix = "-db" + appDirSuffix = "-app" // profileDirPerms is the default permissions applied to the profile // directory (usually ~/.tsh) @@ -177,10 +171,10 @@ func (fs *FSLocalKeyStore) AddKey(host, username string, key *Key) error { if err := writeBytes(username+constants.FileExtSSHCert, key.Cert); err != nil { return trace.Wrap(err) } - if err := writeBytes(username+fileExtTLSCert, key.TLSCert); err != nil { + if err := writeBytes(username+constants.FileExtTLSCert, key.TLSCert); err != nil { return trace.Wrap(err) } - if err := writeBytes(username+fileExtPub, key.Pub); err != nil { + if err := writeBytes(username+constants.FileExtPub, key.Pub); err != nil { return trace.Wrap(err) } if err := writeBytes(username, key.Priv); err != nil { @@ -203,13 +197,13 @@ func (fs *FSLocalKeyStore) AddKey(host, username string, key *Key) error { // don't expect any well-meaning user to create bad names. kubeCluster = filepath.Clean(kubeCluster) - fname := filepath.Join(username+kubeDirSuffix, key.ClusterName, kubeCluster+fileExtTLSCert) + fname := filepath.Join(username+kubeDirSuffix, key.ClusterName, kubeCluster+constants.FileExtTLSCert) if err := writeBytes(fname, cert); err != nil { return trace.Wrap(err) } } for db, cert := range key.DBTLSCerts { - fname := filepath.Join(username+dbDirSuffix, key.ClusterName, filepath.Clean(db)+fileExtTLSCert) + fname := filepath.Join(username+dbDirSuffix, key.ClusterName, filepath.Clean(db)+constants.FileExtTLSCert) if err := os.MkdirAll(filepath.Join(dirPath, filepath.Dir(fname)), os.ModeDir|profileDirPerms); err != nil { return trace.Wrap(err) } @@ -218,7 +212,7 @@ func (fs *FSLocalKeyStore) AddKey(host, username string, key *Key) error { } } for app, cert := range key.AppTLSCerts { - fname := filepath.Join(username+appDirSuffix, key.ClusterName, filepath.Clean(app)+fileExtTLSCert) + fname := filepath.Join(username+appDirSuffix, key.ClusterName, filepath.Clean(app)+constants.FileExtTLSCert) if err := os.MkdirAll(filepath.Join(dirPath, filepath.Dir(fname)), os.ModeDir|profileDirPerms); err != nil { return trace.Wrap(err) } @@ -234,8 +228,8 @@ func (fs *FSLocalKeyStore) DeleteKey(host, username string, opts ...KeyOption) e dirPath := fs.dirFor(host) files := []string{ filepath.Join(dirPath, username+constants.FileExtSSHCert), - filepath.Join(dirPath, username+fileExtTLSCert), - filepath.Join(dirPath, username+fileExtPub), + filepath.Join(dirPath, username+constants.FileExtTLSCert), + filepath.Join(dirPath, username+constants.FileExtPub), filepath.Join(dirPath, username), } for _, fn := range files { @@ -267,7 +261,7 @@ func (fs *FSLocalKeyStore) DeleteKeyOption(host, username string, opts ...KeyOpt // DeleteKeys removes all session keys from disk. func (fs *FSLocalKeyStore) DeleteKeys() error { - dirPath := filepath.Join(fs.KeyDir, sessionKeyDir) + dirPath := filepath.Join(fs.KeyDir, constants.SessionKeyDir) err := os.RemoveAll(dirPath) if err != nil { @@ -292,13 +286,13 @@ func (fs *FSLocalKeyStore) GetKey(proxyHost, username string, opts ...KeyOption) fs.log.Error(err) return nil, trace.Wrap(err) } - tlsCertFile := filepath.Join(dirPath, username+fileExtTLSCert) + tlsCertFile := filepath.Join(dirPath, username+constants.FileExtTLSCert) tlsCert, err := ioutil.ReadFile(tlsCertFile) if err != nil { fs.log.Error(err) return nil, trace.Wrap(err) } - pub, err := ioutil.ReadFile(filepath.Join(dirPath, username+fileExtPub)) + pub, err := ioutil.ReadFile(filepath.Join(dirPath, username+constants.FileExtPub)) if err != nil { fs.log.Error(err) return nil, trace.Wrap(err) @@ -403,7 +397,7 @@ func (o withKubeCerts) getKey(store LocalKeyStore, idx keyIndex, key *Key) error if err != nil { return trace.Wrap(err) } - kubeCluster := strings.TrimSuffix(filepath.Base(fi.Name()), fileExtTLSCert) + kubeCluster := strings.TrimSuffix(filepath.Base(fi.Name()), constants.FileExtTLSCert) key.KubeTLSCerts[kubeCluster] = data } case *MemLocalKeyStore: @@ -469,7 +463,7 @@ func (o withDBCerts) getKey(store LocalKeyStore, idx keyIndex, key *Key) error { if err != nil { return trace.Wrap(err) } - dbName := strings.TrimSuffix(filepath.Base(fi.Name()), fileExtTLSCert) + dbName := strings.TrimSuffix(filepath.Base(fi.Name()), constants.FileExtTLSCert) key.DBTLSCerts[dbName] = data } case *MemLocalKeyStore: @@ -495,7 +489,7 @@ func (o withDBCerts) deleteKey(store LocalKeyStore, idx keyIndex) error { case *FSLocalKeyStore: dirPath := s.dirFor(idx.proxyHost) if o.dbName != "" { - return os.Remove(filepath.Join(dirPath, idx.username+dbDirSuffix, o.teleportClusterName, o.dbName+fileExtTLSCert)) + return os.Remove(filepath.Join(dirPath, idx.username+dbDirSuffix, o.teleportClusterName, o.dbName+constants.FileExtTLSCert)) } return os.RemoveAll(filepath.Join(dirPath, idx.username+dbDirSuffix, o.teleportClusterName)) case *MemLocalKeyStore: @@ -548,7 +542,7 @@ func (o withAppCerts) getKey(store LocalKeyStore, idx keyIndex, key *Key) error if err != nil { return trace.Wrap(err) } - appName := strings.TrimSuffix(filepath.Base(fi.Name()), fileExtTLSCert) + appName := strings.TrimSuffix(filepath.Base(fi.Name()), constants.FileExtTLSCert) key.AppTLSCerts[appName] = data } case *MemLocalKeyStore: @@ -574,7 +568,7 @@ func (o withAppCerts) deleteKey(store LocalKeyStore, idx keyIndex) error { case *FSLocalKeyStore: dirPath := s.dirFor(idx.proxyHost) if o.appName != "" { - return os.Remove(filepath.Join(dirPath, idx.username+appDirSuffix, o.teleportClusterName, o.appName+fileExtTLSCert)) + return os.Remove(filepath.Join(dirPath, idx.username+appDirSuffix, o.teleportClusterName, o.appName+constants.FileExtTLSCert)) } return os.RemoveAll(filepath.Join(dirPath, idx.username+appDirSuffix, o.teleportClusterName)) case *MemLocalKeyStore: @@ -596,28 +590,15 @@ func (o withAppCerts) deleteKey(store LocalKeyStore, idx keyIndex) error { // initKeysDir initializes the keystore root directory. Usually it is ~/.tsh func initKeysDir(dirPath string) (string, error) { - var err error - // not specified? use `~/.tsh` - if dirPath == "" { - u, err := user.Current() - if err != nil { - dirPath = os.TempDir() - } else { - dirPath = u.HomeDir - } - dirPath = filepath.Join(dirPath, defaultKeyDir) - } + dirPath = client.FullProfilePath(dirPath) // create if doesn't exist: - _, err = os.Stat(dirPath) - if err != nil { - if os.IsNotExist(err) { - err = os.MkdirAll(dirPath, os.ModeDir|profileDirPerms) - if err != nil { - return "", trace.ConvertSystemError(err) - } - } else { + if _, err := os.Stat(dirPath); err != nil { + if !os.IsNotExist(err) { return "", trace.Wrap(err) } + if err = os.MkdirAll(dirPath, os.ModeDir|profileDirPerms); err != nil { + return "", trace.ConvertSystemError(err) + } } return dirPath, nil @@ -635,14 +616,14 @@ type fsLocalNonSessionKeyStore struct { // for fs.KeyDir is typically "~/.tsh", sessionKeyDir is typically "keys", // and proxyHost typically has values like "proxy.example.com". func (fs *fsLocalNonSessionKeyStore) dirFor(proxyHost string) string { - return filepath.Join(fs.KeyDir, sessionKeyDir, proxyHost) + return filepath.Join(fs.KeyDir, constants.SessionKeyDir, proxyHost) } // GetCertsPEM returns trusted TLS certificates of certificate authorities PEM // blocks. func (fs *fsLocalNonSessionKeyStore) GetCertsPEM(proxy string) ([][]byte, error) { dir := fs.dirFor(proxy) - data, err := ioutil.ReadFile(filepath.Join(dir, fileNameTLSCerts)) + data, err := ioutil.ReadFile(filepath.Join(dir, constants.FileNameTLSCerts)) if err != nil { return nil, trace.Wrap(err) } @@ -691,7 +672,7 @@ func (fs *fsLocalNonSessionKeyStore) GetCerts(proxy string) (*x509.CertPool, err // GetKnownHostKeys returns all known public keys from 'known_hosts' func (fs *fsLocalNonSessionKeyStore) GetKnownHostKeys(hostname string) ([]ssh.PublicKey, error) { - bytes, err := ioutil.ReadFile(filepath.Join(fs.KeyDir, fileNameKnownHosts)) + bytes, err := ioutil.ReadFile(filepath.Join(fs.KeyDir, constants.FileNameKnownHosts)) if err != nil { if os.IsNotExist(err) { return nil, nil @@ -729,7 +710,7 @@ func (fs *fsLocalNonSessionKeyStore) GetKnownHostKeys(hostname string) ([]ssh.Pu // AddKnownHostKeys adds a new entry to 'known_hosts' file func (fs *fsLocalNonSessionKeyStore) AddKnownHostKeys(hostname string, hostKeys []ssh.PublicKey) (retErr error) { - fp, err := os.OpenFile(filepath.Join(fs.KeyDir, fileNameKnownHosts), os.O_CREATE|os.O_RDWR, 0640) + fp, err := os.OpenFile(filepath.Join(fs.KeyDir, constants.FileNameKnownHosts), os.O_CREATE|os.O_RDWR, 0640) if err != nil { return trace.ConvertSystemError(err) } @@ -782,7 +763,7 @@ func (fs *fsLocalNonSessionKeyStore) SaveCerts(proxy string, cas []auth.TrustedC return trace.ConvertSystemError(err) } - fp, err := os.OpenFile(filepath.Join(dir, fileNameTLSCerts), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0640) + fp, err := os.OpenFile(filepath.Join(dir, constants.FileNameTLSCerts), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0640) if err != nil { return trace.ConvertSystemError(err) } diff --git a/lib/web/sessions.go b/lib/web/sessions.go index ea9fe4bb50943..3e1df875fffb4 100644 --- a/lib/web/sessions.go +++ b/lib/web/sessions.go @@ -729,9 +729,9 @@ func (s *sessionCache) newSessionContextFromSession(session services.WebSession) if err != nil { return nil, trace.Wrap(err) } - userClient, err := auth.NewTLSClient(auth.ClientConfig{ - Addrs: s.authServers, - TLS: tlsConfig, + userClient, err := auth.NewClient(apiclient.Config{ + Addrs: utils.NetAddrsToStrings(s.authServers), + Credentials: []apiclient.Credentials{apiclient.LoadTLS(tlsConfig)}, }) if err != nil { return nil, trace.Wrap(err) diff --git a/tool/tsh/tsh_test.go b/tool/tsh/tsh_test.go index 47b1cfdb9597d..a433f663bf55f 100644 --- a/tool/tsh/tsh_test.go +++ b/tool/tsh/tsh_test.go @@ -30,6 +30,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" + apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/backend" @@ -82,10 +83,10 @@ func (p *cliModules) IsBoringBinary() bool { } func TestOIDCLogin(t *testing.T) { - os.RemoveAll(client.FullProfilePath("")) + os.RemoveAll(apiclient.FullProfilePath("")) modules.SetModules(&cliModules{}) - defer os.RemoveAll(client.FullProfilePath("")) + defer os.RemoveAll(apiclient.FullProfilePath("")) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -209,7 +210,7 @@ func TestOIDCLogin(t *testing.T) { } func TestMakeClient(t *testing.T) { - os.RemoveAll(client.FullProfilePath("")) + os.RemoveAll(apiclient.FullProfilePath("")) var conf CLIConf // empty config won't work: