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

This commit is contained in:
Brian Joerger
2021-03-23 16:35:42 -07:00
committed by GitHub
parent 8492f62d43
commit 32c4ae255f
17 changed files with 405 additions and 145 deletions
+9 -1
View File
@@ -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")
+5 -5
View File
@@ -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 {
+93 -5
View File
@@ -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()
+96 -19
View File
@@ -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
+118 -37
View File
@@ -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)
}
@@ -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
+16 -1
View File
@@ -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"
)
+2
View File
@@ -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
)
+3
View File
@@ -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=
+2 -2
View File
@@ -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{
+2 -1
View File
@@ -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.
+2 -1
View File
@@ -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
+17 -16
View File
@@ -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/<proxy>/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/<proxy>/<user>.
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/<proxy>/<user>-db/<cluster>/<name>-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/<proxy>/<user>-app/<cluster>/<name>-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
+1 -1
View File
@@ -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)
}
+30 -49
View File
@@ -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)
}
+3 -3
View File
@@ -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)
+4 -3
View File
@@ -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: