mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-21 05:55:42 +08:00
Add Credential loader support for tsh profiles. (#5993)
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user