mirror of
https://github.com/gravitational/teleport
synced 2024-10-23 02:32:39 +00:00
Merge pull request #422 from gravitational/ev/scp
SCP/Exec improvements
This commit is contained in:
commit
f6aa265318
|
@ -497,18 +497,18 @@ behind `work.example.com`:
|
|||
|
||||
### Integrating with OpenSSH Servers
|
||||
|
||||
Existing `sshd` servers can be added to a Teleport cluster.
|
||||
Existing `sshd` servers can be added to a Teleport cluster. For that to work, you
|
||||
have to configure `sshd` to trust Teleport CA.
|
||||
|
||||
1. First, you have to export the CA certificate into a file:
|
||||
Export the Teleport CA certificate into a file:
|
||||
|
||||
```bash
|
||||
> tctl authorities --type=user export > cluster-ca.pub
|
||||
```
|
||||
|
||||
2. Then you should copy this file to every node running `sshd`, for example
|
||||
`into /etc/ssh/teleport-ca.pub`
|
||||
Copy this file to every node running `sshd`, for example into `/etc/ssh/teleport-ca.pub`
|
||||
Then update the `sshd` configuration, usually `/etc/ssh/sshd_config`:
|
||||
|
||||
3. Update `sshd` configuration, usually `/etc/ssh/sshd_config`:
|
||||
```
|
||||
TrustedUserCAKeys /etc/ssh/user-ca.pub
|
||||
```
|
||||
|
|
|
@ -128,7 +128,7 @@ func (s *IntSuite) TestAudit(c *check.C) {
|
|||
go func() {
|
||||
cl, err := t.NewClient(s.me.Username, Site, Host, t.GetPortSSHInt())
|
||||
c.Assert(err, check.IsNil)
|
||||
cl.Output = &myTerm
|
||||
cl.Stdout = &myTerm
|
||||
|
||||
err = cl.SSH([]string{}, false, &myTerm)
|
||||
endC <- err
|
||||
|
@ -280,7 +280,7 @@ func (s *IntSuite) TestInteractive(c *check.C) {
|
|||
openSession := func() {
|
||||
cl, err := t.NewClient(s.me.Username, Site, Host, t.GetPortSSHInt())
|
||||
c.Assert(err, check.IsNil)
|
||||
cl.Output = &personA
|
||||
cl.Stdout = &personA
|
||||
// Person A types something into the terminal (including "exit")
|
||||
personA.Type("\aecho hi\n\r\aexit\n\r\a")
|
||||
err = cl.SSH([]string{}, false, &personA)
|
||||
|
@ -302,7 +302,7 @@ func (s *IntSuite) TestInteractive(c *check.C) {
|
|||
}
|
||||
cl, err := t.NewClient(s.me.Username, Site, Host, t.GetPortSSHInt())
|
||||
c.Assert(err, check.IsNil)
|
||||
cl.Output = &personB
|
||||
cl.Stdout = &personB
|
||||
for i := 0; i < 10; i++ {
|
||||
err = cl.Join(session.ID(sessionID), &personB)
|
||||
if err == nil {
|
||||
|
@ -377,7 +377,7 @@ func (s *IntSuite) TestTwoSites(c *check.C) {
|
|||
|
||||
// directly:
|
||||
tc, err := a.NewClient(username, "site-A", Host, sshPort)
|
||||
tc.Output = &outputA
|
||||
tc.Stdout = &outputA
|
||||
c.Assert(err, check.IsNil)
|
||||
err = tc.SSH(cmd, false, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
@ -385,7 +385,7 @@ func (s *IntSuite) TestTwoSites(c *check.C) {
|
|||
|
||||
// via tunnel b->a:
|
||||
tc, err = b.NewClient(username, "site-A", Host, sshPort)
|
||||
tc.Output = &outputB
|
||||
tc.Stdout = &outputB
|
||||
c.Assert(err, check.IsNil)
|
||||
err = tc.SSH(cmd, false, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
|
|
@ -191,6 +191,11 @@ func (n *nauth) GenerateUserCert(pkey, key []byte, teleportUsername string, allo
|
|||
ValidBefore: validBefore,
|
||||
CertType: ssh.UserCert,
|
||||
}
|
||||
cert.Permissions.Extensions = map[string]string{
|
||||
"permit-pty": "",
|
||||
"permit-port-forwarding": "",
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(pkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -342,12 +342,8 @@ func (s *AuthTunnel) handleDirectTCPIPRequest(sconn *ssh.ServerConn, sshChannel
|
|||
|
||||
func (s *AuthTunnel) keyAuth(
|
||||
conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
cid := fmt.Sprintf(
|
||||
"conn(%v->%v, user=%v)", conn.RemoteAddr(),
|
||||
conn.LocalAddr(), conn.User())
|
||||
|
||||
log.Infof("%v auth attempt with key %v", cid, key.Type())
|
||||
|
||||
log.Infof("keyAuth: %v->%v, user=%v", conn.RemoteAddr(), conn.LocalAddr(), conn.User())
|
||||
cert, ok := key.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return nil, trace.Errorf("ERROR: Server doesn't support provided key type")
|
||||
|
@ -549,7 +545,6 @@ func NewTunClient(purpose string,
|
|||
if user == "" {
|
||||
return nil, trace.BadParameter("SSH connection requires a valid username")
|
||||
}
|
||||
|
||||
tc := &TunClient{
|
||||
purpose: purpose,
|
||||
user: user,
|
||||
|
@ -570,14 +565,11 @@ func NewTunClient(purpose string,
|
|||
|
||||
// use local information about auth servers if it's available
|
||||
if tc.addrStorage != nil {
|
||||
authServers, err := tc.addrStorage.GetAddresses()
|
||||
cachedAuthServers, err := tc.addrStorage.GetAddresses()
|
||||
if err != nil {
|
||||
if !trace.IsNotFound(err) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
log.Infof("local storage is provided, not initialized")
|
||||
log.Errorf("unable to load from auth server cache: %v", err)
|
||||
} else {
|
||||
tc.setAuthServers(authServers)
|
||||
tc.setAuthServers(cachedAuthServers)
|
||||
}
|
||||
}
|
||||
return tc, nil
|
||||
|
|
|
@ -18,7 +18,6 @@ package client
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
@ -99,8 +98,13 @@ type Config struct {
|
|||
// use its own session store,
|
||||
AuthMethods []ssh.AuthMethod
|
||||
|
||||
// Output is a writer, if not passed, stdout will be used
|
||||
Output io.Writer
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Stdin io.Reader
|
||||
|
||||
// ExitStatus carries the returned value (exit status) of the remote
|
||||
// process execution (via SSh exec)
|
||||
ExitStatus int
|
||||
|
||||
// SiteName specifies site to execute operation,
|
||||
// if omitted, first available site will be selected
|
||||
|
@ -194,10 +198,12 @@ func NewClient(c *Config) (tc *TeleportClient, err error) {
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if tc.Output == nil {
|
||||
tc.Output = os.Stdout
|
||||
if tc.Stdout == nil {
|
||||
tc.Stdout = os.Stdout
|
||||
}
|
||||
if tc.Stderr == nil {
|
||||
tc.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
if tc.HostKeyCallback == nil {
|
||||
tc.HostKeyCallback = tc.localAgent.CheckHostSignature
|
||||
}
|
||||
|
@ -251,6 +257,8 @@ func (tc *TeleportClient) getTargetNodes(proxy *ProxyClient) ([]string, error) {
|
|||
|
||||
// SSH connects to a node and, if 'command' is specified, executes the command on it,
|
||||
// otherwise runs interactive shell
|
||||
//
|
||||
// Returns nil if successful, or (possibly) *exec.ExitError
|
||||
func (tc *TeleportClient) SSH(command []string, runLocally bool, input io.Reader) error {
|
||||
// connect to proxy first:
|
||||
if !tc.Config.ProxySpecified() {
|
||||
|
@ -385,7 +393,7 @@ func (tc *TeleportClient) Join(sessionID session.ID, input io.Reader) (err error
|
|||
return tc.runShell(nc, session.ID, input)
|
||||
}
|
||||
|
||||
// SCP securely copies file(s) from one SSH server to another
|
||||
// Play replays the recorded session
|
||||
func (tc *TeleportClient) Play(sessionId string) (err error) {
|
||||
sid, err := session.ParseID(sessionId)
|
||||
if err != nil {
|
||||
|
@ -497,6 +505,14 @@ func (tc *TeleportClient) SCP(args []string, port int, recursive bool) (err erro
|
|||
}
|
||||
defer proxyClient.Close()
|
||||
|
||||
// gets called to convert SSH error code to tc.ExitStatus
|
||||
onError := func(err error) error {
|
||||
exitError, _ := trace.Unwrap(err).(*ssh.ExitError)
|
||||
if exitError != nil {
|
||||
tc.ExitStatus = exitError.ExitStatus()
|
||||
}
|
||||
return err
|
||||
}
|
||||
// upload:
|
||||
if isRemoteDest(last) {
|
||||
login, host, dest := parseSCPDestination(last)
|
||||
|
@ -511,11 +527,11 @@ func (tc *TeleportClient) SCP(args []string, port int, recursive bool) (err erro
|
|||
}
|
||||
// copy everything except the last arg (that's destination)
|
||||
for _, src := range args[:len(args)-1] {
|
||||
err = client.Upload(src, dest)
|
||||
err = client.Upload(src, dest, tc.Stderr)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
return onError(err)
|
||||
}
|
||||
fmt.Printf("uploaded %s\n", src)
|
||||
fmt.Printf("Uploaded %s\n", src)
|
||||
}
|
||||
// download:
|
||||
} else {
|
||||
|
@ -530,11 +546,11 @@ func (tc *TeleportClient) SCP(args []string, port int, recursive bool) (err erro
|
|||
}
|
||||
// copy everything except the last arg (that's destination)
|
||||
for _, dest := range args[1:] {
|
||||
err = client.Download(src, dest, recursive)
|
||||
err = client.Download(src, dest, recursive, tc.Stderr)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
return onError(err)
|
||||
}
|
||||
fmt.Printf("downloaded %s\n", src)
|
||||
fmt.Printf("Downloaded %s\n", src)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -578,6 +594,11 @@ func (tc *TeleportClient) ListNodes() ([]services.Server, error) {
|
|||
|
||||
// runCommand executes a given bash command on a bunch of remote nodes
|
||||
func (tc *TeleportClient) runCommand(siteName string, nodeAddresses []string, proxyClient *ProxyClient, command []string) error {
|
||||
stdin := tc.Stdin
|
||||
// do not pass terminal input into SSH commands
|
||||
if stdin == os.Stdin && terminal.IsTerminal(int(os.Stdin.Fd())) {
|
||||
stdin = nil
|
||||
}
|
||||
resultsC := make(chan error, len(nodeAddresses))
|
||||
for _, address := range nodeAddresses {
|
||||
go func(address string) {
|
||||
|
@ -588,38 +609,30 @@ func (tc *TeleportClient) runCommand(siteName string, nodeAddresses []string, pr
|
|||
var nodeClient *NodeClient
|
||||
nodeClient, err = proxyClient.ConnectToNode(address+"@"+siteName, tc.Config.HostLogin)
|
||||
if err != nil {
|
||||
fmt.Fprintln(tc.Output, err)
|
||||
fmt.Fprintln(tc.Stderr, err)
|
||||
return
|
||||
}
|
||||
defer nodeClient.Close()
|
||||
|
||||
// run the command on one node:
|
||||
out := bytes.Buffer{}
|
||||
err = nodeClient.Run(command, &out)
|
||||
if err != nil {
|
||||
fmt.Fprintln(tc.Output, err)
|
||||
}
|
||||
if len(nodeAddresses) > 1 {
|
||||
fmt.Printf("Running command on %v:\n", address)
|
||||
}
|
||||
|
||||
if tc.Output != nil {
|
||||
if err != nil {
|
||||
fmt.Fprintln(tc.Output, err)
|
||||
} else {
|
||||
fmt.Fprintf(tc.Output, out.String())
|
||||
err = nodeClient.Run(command, stdin, tc.Stdout, tc.Stderr)
|
||||
if err != nil {
|
||||
exitErr, ok := err.(*ssh.ExitError)
|
||||
if ok {
|
||||
tc.ExitStatus = exitErr.ExitStatus()
|
||||
}
|
||||
}
|
||||
}(address)
|
||||
}
|
||||
|
||||
var lastError error
|
||||
for range nodeAddresses {
|
||||
if err := <-resultsC; err != nil {
|
||||
lastError = err
|
||||
}
|
||||
}
|
||||
|
||||
return trace.Wrap(lastError)
|
||||
}
|
||||
|
||||
|
@ -633,8 +646,11 @@ func (tc *TeleportClient) runShell(nodeClient *NodeClient, sessionID session.ID,
|
|||
if stdin == nil {
|
||||
stdin = os.Stdin
|
||||
}
|
||||
if tc.Output == nil {
|
||||
tc.Output = os.Stdout
|
||||
if tc.Stdout == nil {
|
||||
tc.Stdout = os.Stdout
|
||||
}
|
||||
if tc.Stderr == nil {
|
||||
tc.Stderr = os.Stderr
|
||||
}
|
||||
// terminal must be in raw mode
|
||||
var (
|
||||
|
@ -698,7 +714,6 @@ func (tc *TeleportClient) runShell(nodeClient *NodeClient, sessionID session.ID,
|
|||
go func() {
|
||||
for {
|
||||
<-ctrlZSignal
|
||||
fmt.Println("---> 3")
|
||||
_, err := shell.Write([]byte{26})
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
|
@ -709,7 +724,7 @@ func (tc *TeleportClient) runShell(nodeClient *NodeClient, sessionID session.ID,
|
|||
// copy from the remote shell to the local
|
||||
go func() {
|
||||
defer broadcastClose.Close()
|
||||
_, err := io.Copy(tc.Output, shell)
|
||||
_, err := io.Copy(tc.Stdout, shell)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
|
@ -775,7 +790,6 @@ func (tc *TeleportClient) ConnectToProxy() (*ProxyClient, error) {
|
|||
for _, m := range tc.authMethods() {
|
||||
sshConfig.Auth = []ssh.AuthMethod{m}
|
||||
proxyClient, err := ssh.Dial("tcp", proxyAddr, sshConfig)
|
||||
log.Infof("ssh.Dial error: %v", err)
|
||||
if err != nil {
|
||||
if utils.IsHandshakeFailedError(err) {
|
||||
continue
|
||||
|
|
|
@ -205,15 +205,15 @@ func (proxy *ProxyClient) ConnectToNode(nodeAddress string, user string) (*NodeC
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
printErrors := func() {
|
||||
buf := &bytes.Buffer{}
|
||||
io.Copy(buf, proxyErr)
|
||||
if buf.String() != "" {
|
||||
fmt.Println("ERROR: " + buf.String())
|
||||
n, _ := io.Copy(os.Stderr, proxyErr)
|
||||
if n > 0 {
|
||||
os.Stderr.WriteString("\n")
|
||||
}
|
||||
}
|
||||
err = proxySession.RequestSubsystem("proxy:" + nodeAddress)
|
||||
if err != nil {
|
||||
defer printErrors()
|
||||
|
||||
parts := strings.Split(nodeAddress, "@")
|
||||
siteName := parts[len(parts)-1]
|
||||
return nil, trace.Errorf("Failed connecting to cluster %v: %v", siteName, err)
|
||||
|
@ -247,9 +247,9 @@ func (proxy *ProxyClient) ConnectToNode(nodeAddress string, user string) (*NodeC
|
|||
return &NodeClient{Client: client, Proxy: proxy}, nil
|
||||
}
|
||||
if utils.IsHandshakeFailedError(e) {
|
||||
// remoe the name of the site from the node address:
|
||||
// remove the name of the site from the node address:
|
||||
parts := strings.Split(nodeAddress, "@")
|
||||
return nil, trace.Errorf(`access denied to login "%v" when connecting to %v`, user, parts[0])
|
||||
return nil, trace.Errorf(`access denied to login "%v" when connecting to %v: %v`, user, parts[0], e)
|
||||
}
|
||||
return nil, e
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ func (client *NodeClient) Shell(width, height int, sessionID session.ID) (io.Rea
|
|||
if len(sessionID) > 0 {
|
||||
err = clientSession.Setenv(sshutils.SessionEnvVar, string(sessionID))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,11 +395,7 @@ func (client *NodeClient) Shell(width, height int, sessionID session.ID) (io.Rea
|
|||
}()
|
||||
|
||||
go func() {
|
||||
buf := &bytes.Buffer{}
|
||||
io.Copy(buf, stderr)
|
||||
if buf.String() != "" {
|
||||
fmt.Println("ERROR: " + buf.String())
|
||||
}
|
||||
io.Copy(os.Stderr, stderr)
|
||||
}()
|
||||
|
||||
err = clientSession.Shell()
|
||||
|
@ -418,23 +414,19 @@ func (client *NodeClient) Shell(width, height int, sessionID session.ID) (io.Rea
|
|||
|
||||
// Run executes command on the remote server and writes its stdout to
|
||||
// the 'output' argument
|
||||
func (client *NodeClient) Run(cmd []string, output io.Writer) error {
|
||||
func (client *NodeClient) Run(cmd []string, stdin io.Reader, stdout, stderr io.Writer) error {
|
||||
session, err := client.Client.NewSession()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
session.Stdout = output
|
||||
|
||||
if err := session.Run(strings.Join(cmd, " ")); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
session.Stdout = stdout
|
||||
session.Stderr = stderr
|
||||
session.Stdin = stdin
|
||||
return session.Run(strings.Join(cmd, " "))
|
||||
}
|
||||
|
||||
// Upload uploads file or dir to the remote server
|
||||
func (client *NodeClient) Upload(localSourcePath, remoteDestinationPath string) error {
|
||||
func (client *NodeClient) Upload(localSourcePath, remoteDestinationPath string, stderr io.Writer) error {
|
||||
file, err := os.Open(localSourcePath)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -460,11 +452,11 @@ func (client *NodeClient) Upload(localSourcePath, remoteDestinationPath string)
|
|||
}
|
||||
shellCmd += " " + remoteDestinationPath
|
||||
|
||||
return client.scp(scpConf, shellCmd)
|
||||
return client.scp(scpConf, shellCmd, stderr)
|
||||
}
|
||||
|
||||
// Download downloads file or dir from the remote server
|
||||
func (client *NodeClient) Download(remoteSourcePath, localDestinationPath string, isDir bool) error {
|
||||
func (client *NodeClient) Download(remoteSourcePath, localDestinationPath string, isDir bool, stderr io.Writer) error {
|
||||
scpConf := scp.Command{
|
||||
Sink: true,
|
||||
TargetIsDir: isDir,
|
||||
|
@ -479,12 +471,12 @@ func (client *NodeClient) Download(remoteSourcePath, localDestinationPath string
|
|||
}
|
||||
shellCmd += " " + remoteSourcePath
|
||||
|
||||
return client.scp(scpConf, shellCmd)
|
||||
return client.scp(scpConf, shellCmd, stderr)
|
||||
}
|
||||
|
||||
// scp runs remote scp command(shellCmd) on the remote server and
|
||||
// runs local scp handler using scpConf
|
||||
func (client *NodeClient) scp(scpCommand scp.Command, shellCmd string) error {
|
||||
func (client *NodeClient) scp(scpCommand scp.Command, shellCmd string, errWriter io.Writer) error {
|
||||
session, err := client.Client.NewSession()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -501,22 +493,6 @@ func (client *NodeClient) scp(scpCommand scp.Command, shellCmd string) error {
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
stderr, err := session.StderrPipe()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
serverErrors := make(chan error, 2)
|
||||
go func() {
|
||||
var errMsg bytes.Buffer
|
||||
io.Copy(&errMsg, stderr)
|
||||
if len(errMsg.Bytes()) > 0 {
|
||||
serverErrors <- trace.Errorf(errMsg.String())
|
||||
} else {
|
||||
close(serverErrors)
|
||||
}
|
||||
}()
|
||||
|
||||
ch := utils.NewPipeNetConn(
|
||||
stdout,
|
||||
stdin,
|
||||
|
@ -525,20 +501,24 @@ func (client *NodeClient) scp(scpCommand scp.Command, shellCmd string) error {
|
|||
&net.IPAddr{},
|
||||
)
|
||||
|
||||
closeC := make(chan interface{}, 1)
|
||||
go func() {
|
||||
err := scpCommand.Execute(ch)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
if err = scpCommand.Execute(ch); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
stdin.Close()
|
||||
close(closeC)
|
||||
}()
|
||||
|
||||
err = session.Run(shellCmd)
|
||||
|
||||
select {
|
||||
case serverError := <-serverErrors:
|
||||
return trace.Wrap(serverError)
|
||||
runErr := session.Run(shellCmd)
|
||||
if runErr != nil && err == nil {
|
||||
err = runErr
|
||||
}
|
||||
<-closeC
|
||||
if trace.IsEOF(err) {
|
||||
err = nil
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// listenAndForward listens on a given socket and forwards all incoming connections
|
||||
|
|
|
@ -99,9 +99,13 @@ func (a *LocalKeyAgent) AddHostSignersToCache(hostSigners []services.CertAuthori
|
|||
// CheckHostSignature checks if the given host key was signed by one of the trusted
|
||||
// certificaate authorities (CAs)
|
||||
func (a *LocalKeyAgent) CheckHostSignature(hostId string, remote net.Addr, key ssh.PublicKey) error {
|
||||
// TODO (ev) remove this!
|
||||
// we're temporarily turning off host validation to experiment with OpenSSH servers
|
||||
//return nil
|
||||
|
||||
cert, ok := key.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return trace.BadParameter("expected certificate")
|
||||
return trace.BadParameter("Cannot trust %v. Expected a certificate", remote.String())
|
||||
}
|
||||
keys, err := a.keyStore.GetKnownCAs()
|
||||
if err != nil {
|
||||
|
|
|
@ -133,18 +133,10 @@ func (process *TeleportProcess) connectToAuthService(role teleport.Role) (*Conne
|
|||
storage := utils.NewFileAddrStorage(
|
||||
filepath.Join(process.Config.DataDir, "authservers.json"))
|
||||
|
||||
var authServers []utils.NetAddr
|
||||
authServers, err = storage.GetAddresses()
|
||||
if err != nil && len(authServers) == 0 {
|
||||
log.Infof("no auth servers are available from the local storage")
|
||||
authServers = process.Config.AuthServers
|
||||
}
|
||||
|
||||
log.Infof("connecting to auth servers: %v", authServers)
|
||||
authUser := identity.Cert.ValidPrincipals[0]
|
||||
authClient, err := auth.NewTunClient(
|
||||
string(role),
|
||||
authServers,
|
||||
process.Config.AuthServers,
|
||||
authUser,
|
||||
[]ssh.AuthMethod{ssh.PublicKeys(identity.KeySigner)},
|
||||
auth.TunClientStorage(storage),
|
||||
|
@ -391,7 +383,6 @@ func (process *TeleportProcess) initAuthService(authority auth.Authority) error
|
|||
}
|
||||
// immediately register, and then keep repeating in a loop:
|
||||
for !askedToExit {
|
||||
log.Infof("[AUTH] heartbeat listening on %s, announcing %s", cfg.Auth.SSHAddr.Addr, srv.Addr)
|
||||
err := authServer.UpsertAuthServer(srv, defaults.ServerHeartbeatTTL)
|
||||
if err != nil {
|
||||
log.Warningf("failed to announce presence: %v", err)
|
||||
|
|
140
lib/srv/exec.go
140
lib/srv/exec.go
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package srv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
@ -31,14 +30,22 @@ import (
|
|||
"github.com/gravitational/teleport/lib/events"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/kardianos/osext"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// execResult is used internally to send the result of a command execution from
|
||||
// a goroutine to SSH request handler and back to the calling client
|
||||
type execResult struct {
|
||||
command string
|
||||
code int
|
||||
|
||||
// returned exec code
|
||||
code int
|
||||
|
||||
// stderr output
|
||||
stderr []byte
|
||||
}
|
||||
|
||||
type execReq struct {
|
||||
|
@ -51,23 +58,34 @@ type execResponse struct {
|
|||
cmdName string
|
||||
cmd *exec.Cmd
|
||||
ctx *ctx
|
||||
|
||||
// TODO(klizhentas) implement capturing as a threadsafe, factored out feature
|
||||
// that uses protected writes & reads to the buffer
|
||||
|
||||
// 'out' contains captured command output
|
||||
out *bytes.Buffer
|
||||
}
|
||||
|
||||
// parseExecRequest parses SSH exec request
|
||||
func parseExecRequest(req *ssh.Request, ctx *ctx) (*execResponse, error) {
|
||||
var e execReq
|
||||
if err := ssh.Unmarshal(req.Payload, &e); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse exec request, error: %v", err)
|
||||
}
|
||||
// is this scp request?
|
||||
args := strings.Split(e.Command, " ")
|
||||
if len(args) > 0 {
|
||||
_, f := filepath.Split(args[0])
|
||||
if f == "scp" {
|
||||
// for 'scp' requests, we'll fork ourselves with scp parameters:
|
||||
teleportBin, err := osext.Executable()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
e.Command = fmt.Sprintf("%s scp --remote-addr=%s --local-addr=%s %v",
|
||||
teleportBin,
|
||||
ctx.conn.RemoteAddr().String(),
|
||||
ctx.conn.LocalAddr().String(),
|
||||
strings.Join(args[1:], " "))
|
||||
}
|
||||
}
|
||||
return &execResponse{
|
||||
ctx: ctx,
|
||||
cmdName: e.Command,
|
||||
out: &bytes.Buffer{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -75,12 +93,37 @@ func (e *execResponse) String() string {
|
|||
return fmt.Sprintf("Exec(cmd=%v)", e.cmdName)
|
||||
}
|
||||
|
||||
// prepareOSCommand configures os.Cmd for executing a given command within an SSH
|
||||
// prepareShell configures exec.Cmd object for launching shell for an SSH user
|
||||
func prepareShell(ctx *ctx) (*exec.Cmd, error) {
|
||||
// determine shell for the given OS user:
|
||||
shellCommand, err := utils.GetLoginShell(ctx.login)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
// in test mode short-circuit to /bin/sh
|
||||
if ctx.isTestStub {
|
||||
shellCommand = "/bin/sh"
|
||||
}
|
||||
c, err := prepareCommand(ctx, shellCommand)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
// this configures shell to run in 'login' mode
|
||||
c.Args[0] = "-" + filepath.Base(shellCommand)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// prepareCommand configures exec.Cmd for executing a given command within an SSH
|
||||
// session.
|
||||
//
|
||||
// If args are empty, it means a simple shell must be launched
|
||||
// Otherwise, a shell launches with "-c args" as parameters
|
||||
func prepareOSCommand(ctx *ctx, args ...string) (*exec.Cmd, error) {
|
||||
// 'cmd' is the string passed as parameter to 'ssh' command, like "ls -l /"
|
||||
//
|
||||
// If 'cmd' does not have any spaces in it, it gets executed directly, otherwise
|
||||
// it is passed to user's shell for interpretation
|
||||
func prepareCommand(ctx *ctx, cmd string) (*exec.Cmd, error) {
|
||||
args := strings.Split(cmd, " ")
|
||||
|
||||
osUserName := ctx.login
|
||||
// configure UID & GID of the requested OS user:
|
||||
osUser, err := user.Lookup(osUserName)
|
||||
|
@ -95,27 +138,14 @@ func prepareOSCommand(ctx *ctx, args ...string) (*exec.Cmd, error) {
|
|||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
curUser, err := user.Current()
|
||||
// get user's shell:
|
||||
shell, err := utils.GetLoginShell(ctx.login)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
// determine shell for the given OS user:
|
||||
shellCommand, err := utils.GetLoginShell(osUserName)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
// in test mode short-circuit to /bin/sh
|
||||
if ctx.isTestStub {
|
||||
shellCommand = "/bin/sh"
|
||||
shell = "/bin/sh"
|
||||
}
|
||||
if len(args) > 0 {
|
||||
orig := args
|
||||
args = []string{"-c"}
|
||||
args = append(args, orig...)
|
||||
}
|
||||
|
||||
// try to determine the host name of the 1st available proxy to set a nicer
|
||||
// session URL. fall back to <proxyhost> placeholder
|
||||
proxyHost := "<proxyhost>"
|
||||
|
@ -128,26 +158,27 @@ func prepareOSCommand(ctx *ctx, args ...string) (*exec.Cmd, error) {
|
|||
proxyHost = proxies[0].Hostname
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("created OS command '%s' with params: '%v'", shellCommand, args)
|
||||
c := exec.Command(shellCommand, args...)
|
||||
var c *exec.Cmd
|
||||
if len(args) == 1 {
|
||||
c = exec.Command(args[0])
|
||||
} else {
|
||||
c = exec.Command(shell, append([]string{"-c", cmd})...)
|
||||
}
|
||||
c.Env = []string{
|
||||
"TERM=xterm",
|
||||
"LANG=en_US.UTF-8",
|
||||
"HOME=" + osUser.HomeDir,
|
||||
"USER=" + osUserName,
|
||||
"SHELL=" + c.Path,
|
||||
"SHELL=" + shell,
|
||||
"SSH_TELEPORT_USER=" + ctx.teleportUser,
|
||||
fmt.Sprintf("SSH_SESSION_WEBPROXY_ADDR=%s:3080", proxyHost),
|
||||
}
|
||||
// this configures shell to run in 'login' mode
|
||||
c.Args[0] = "-" + filepath.Base(shellCommand)
|
||||
c.Dir = osUser.HomeDir
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
|
||||
// execute the command under requested user's UID:GID
|
||||
if curUser.Uid != osUser.Uid {
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
|
||||
// apply environment variables passed from the client
|
||||
for n, v := range ctx.env {
|
||||
c.Env = append(c.Env, fmt.Sprintf("%s=%s", n, v))
|
||||
|
@ -179,28 +210,30 @@ func prepareOSCommand(ctx *ctx, args ...string) (*exec.Cmd, error) {
|
|||
|
||||
// start launches the given command returns (nil, nil) if successful. execResult is only used
|
||||
// to communicate an error while launching
|
||||
func (e *execResponse) start(sconn *ssh.ServerConn, shell string, ch ssh.Channel) (*execResult, error) {
|
||||
func (e *execResponse) start(ch ssh.Channel) (*execResult, error) {
|
||||
var err error
|
||||
e.cmd, err = prepareOSCommand(e.ctx, e.cmdName)
|
||||
e.cmd, err = prepareCommand(e.ctx, e.cmdName)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
e.cmd.Stderr = ch.Stderr()
|
||||
e.cmd.Stdout = ch
|
||||
|
||||
// capture output to the buffer
|
||||
e.cmd.Stdout = io.MultiWriter(e.out, ch)
|
||||
e.cmd.Stderr = io.MultiWriter(e.out, ch)
|
||||
|
||||
// TODO(klizhentas) figure out the way to see if stdin is ever needed.
|
||||
// e.cmd.Stdin = ch leads to the following problem:
|
||||
// e.cmd.Wait() never returns because stdin never gets closed or never reached
|
||||
// see cmd.Stdin comments
|
||||
e.cmd.Stdin = nil
|
||||
inputWriter, err := e.cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
go func() {
|
||||
io.Copy(inputWriter, ch)
|
||||
inputWriter.Close()
|
||||
}()
|
||||
|
||||
if err := e.cmd.Start(); err != nil {
|
||||
e.ctx.Warningf("%v start failure err: %v", e, err)
|
||||
return e.collectStatus(e.cmd, trace.ConvertSystemError(err))
|
||||
}
|
||||
e.ctx.Infof("%v started", e)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -208,7 +241,8 @@ func (e *execResponse) wait() (*execResult, error) {
|
|||
if e.cmd.Process == nil {
|
||||
e.ctx.Errorf("no process")
|
||||
}
|
||||
return e.collectStatus(e.cmd, e.cmd.Wait())
|
||||
err := e.cmd.Wait()
|
||||
return e.collectStatus(e.cmd, err)
|
||||
}
|
||||
|
||||
func (e *execResponse) collectStatus(cmd *exec.Cmd, err error) (*execResult, error) {
|
||||
|
@ -227,7 +261,9 @@ func (e *execResponse) collectStatus(cmd *exec.Cmd, err error) (*execResult, err
|
|||
}
|
||||
if err != nil {
|
||||
fields[events.ExecEventError] = err.Error()
|
||||
fields[events.ExecEventCode] = strconv.Itoa(status.code)
|
||||
if status != nil {
|
||||
fields[events.ExecEventCode] = strconv.Itoa(status.code)
|
||||
}
|
||||
}
|
||||
auditLog.EmitAuditEvent(events.ExecEvent, fields)
|
||||
return status, err
|
||||
|
|
|
@ -65,7 +65,7 @@ func (s *ExecSuite) TestOSCommandPrep(c *check.C) {
|
|||
}
|
||||
|
||||
// empty command (simple shell)
|
||||
cmd, err := prepareOSCommand(s.ctx)
|
||||
cmd, err := prepareShell(s.ctx)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(cmd, check.NotNil)
|
||||
c.Assert(cmd.Path, check.Equals, "/bin/sh")
|
||||
|
@ -74,13 +74,20 @@ func (s *ExecSuite) TestOSCommandPrep(c *check.C) {
|
|||
c.Assert(cmd.Env, check.DeepEquals, expectedEnv)
|
||||
|
||||
// non-empty command (exec a prog)
|
||||
cmd, err = prepareOSCommand(s.ctx, "ls -lh")
|
||||
s.ctx.isTestStub = true
|
||||
cmd, err = prepareCommand(s.ctx, "ls -lh /etc")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(cmd, check.NotNil)
|
||||
c.Assert(cmd.Path, check.Equals, "/bin/sh")
|
||||
c.Assert(cmd.Args, check.DeepEquals, []string{"-sh", "-c", "ls -lh"})
|
||||
c.Assert(cmd.Args, check.DeepEquals, []string{"/bin/sh", "-c", "ls -lh /etc"})
|
||||
c.Assert(cmd.Dir, check.Equals, s.usr.HomeDir)
|
||||
c.Assert(cmd.Env, check.DeepEquals, expectedEnv)
|
||||
|
||||
// command without args
|
||||
cmd, err = prepareCommand(s.ctx, "top")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(cmd.Path, check.Equals, "/usr/bin/top")
|
||||
c.Assert(cmd.Args, check.DeepEquals, []string{"top"})
|
||||
}
|
||||
|
||||
// implementation of ssh.Conn interface
|
||||
|
|
|
@ -446,7 +446,7 @@ func (s *session) startShell(ch ssh.Channel, ctx *ctx) error {
|
|||
}
|
||||
}
|
||||
// prepare environment & Launch shell:
|
||||
cmd, err := prepareOSCommand(ctx)
|
||||
cmd, err := prepareShell(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ package srv
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
@ -40,7 +39,6 @@ import (
|
|||
"github.com/gravitational/teleport/lib/services"
|
||||
rsession "github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/gravitational/teleport/lib/sshutils/scp"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
@ -434,7 +432,6 @@ func (s *Server) isAuthority(cert ssh.PublicKey) bool {
|
|||
}
|
||||
}
|
||||
}
|
||||
log.Warningf("no matching authority found")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -692,6 +689,10 @@ func (s *Server) handleSessionRequests(sconn *ssh.ServerConn, ch ssh.Channel, in
|
|||
}
|
||||
case result := <-ctx.result:
|
||||
ctx.Infof("ctx.result = %v", result)
|
||||
// pass back stderr output
|
||||
if result.stderr != nil {
|
||||
ch.Stderr().Write(result.stderr)
|
||||
}
|
||||
// this means that exec process has finished and delivered the execution result,
|
||||
// we send it back and close the session
|
||||
_, err := ch.SendRequest("exit-status", false, ssh.Marshal(struct{ C uint32 }{C: uint32(result.code)}))
|
||||
|
@ -851,6 +852,8 @@ func (s *Server) handlePTYReq(ch ssh.Channel, req *ssh.Request, ctx *ctx) error
|
|||
|
||||
// handleExec is responsible for executing 'exec' SSH requests (i.e. executing
|
||||
// a command after making an SSH connection)
|
||||
//
|
||||
// Note: this also handles 'scp' requests because 'scp' is a subset of "exec"
|
||||
func (s *Server) handleExec(ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
|
||||
execResponse, err := parseExecRequest(req, ctx)
|
||||
if err != nil {
|
||||
|
@ -858,17 +861,10 @@ func (s *Server) handleExec(ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
|
|||
replyError(ch, req, err)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if scp.IsSCP(execResponse.cmdName) {
|
||||
ctx.Infof("detected SCP command: %v", execResponse.cmdName)
|
||||
if err := s.handleSCP(ch, req, ctx, execResponse.cmdName); err != nil {
|
||||
ctx.Warningf("handleSCP() err: %v", err)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return ch.Close()
|
||||
if req.WantReply {
|
||||
req.Reply(true, nil)
|
||||
}
|
||||
|
||||
result, err := execResponse.start(ctx.conn, s.shell, ch)
|
||||
result, err := execResponse.start(ch)
|
||||
if err != nil {
|
||||
ctx.Infof("error starting command, %v", err)
|
||||
replyError(ch, req, err)
|
||||
|
@ -880,45 +876,17 @@ func (s *Server) handleExec(ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
|
|||
// in case if result is nil and no error, this means that program is
|
||||
// running in the background
|
||||
go func() {
|
||||
ctx.Infof("%v waiting for result", execResponse)
|
||||
result, err := execResponse.wait()
|
||||
if err != nil {
|
||||
ctx.Infof("%v wait failed: %v", execResponse, err)
|
||||
ctx.Errorf("%v wait failed: %v", execResponse, err)
|
||||
}
|
||||
if result != nil {
|
||||
ctx.Infof("%v result collected: %v", execResponse, result)
|
||||
ctx.sendResult(*result)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSCP processes SSH requests for uploading or downloading files via `scp` command
|
||||
func (s *Server) handleSCP(ch ssh.Channel, req *ssh.Request, ctx *ctx, args string) error {
|
||||
cmd, err := scp.ParseCommand(args, ctx.conn, s.alog)
|
||||
if err != nil {
|
||||
ctx.Warningf("failed to parse command: %v", cmd)
|
||||
return trace.Wrap(err, fmt.Sprintf("failure to parse command '%v'", cmd))
|
||||
}
|
||||
ctx.Infof("handleSCP(cmd=%#v)", cmd)
|
||||
|
||||
cmdBytes, _ := json.MarshalIndent(cmd, "", "\t")
|
||||
ctx.Infof("SCP command:\n%s\n", string(cmdBytes))
|
||||
|
||||
// TODO(klizhentas) current version of handling exec is incorrect.
|
||||
// req.Reply should be sent as long as command start is done,
|
||||
// not at the end. This is my fix for SCP only:
|
||||
req.Reply(true, nil)
|
||||
if err := cmd.Execute(ch); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
_, err = ch.SendRequest("exit-status", false, ssh.Marshal(struct{ C uint32 }{C: uint32(0)}))
|
||||
if err != nil {
|
||||
ctx.Errorf("failed to send scp exit status: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func replyError(ch ssh.Channel, req *ssh.Request, err error) {
|
||||
message := []byte(utils.UserMessageFromError(err))
|
||||
io.Copy(ch.Stderr(), bytes.NewBuffer(message))
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gravitational/teleport/lib/events"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
|
@ -55,49 +56,51 @@ type Command struct {
|
|||
Target string
|
||||
Recursive bool
|
||||
User *user.User
|
||||
Conn ssh.ConnMetadata
|
||||
AuditLog events.IAuditLog
|
||||
//Conn ssh.ConnMetadata
|
||||
AuditLog events.IAuditLog
|
||||
|
||||
// Ev:
|
||||
RemoteAddr string
|
||||
LocalAddr string
|
||||
}
|
||||
|
||||
// Execute implements SSH file copy (SCP)
|
||||
func (cmd *Command) Execute(ch io.ReadWriter) error {
|
||||
func (cmd *Command) Execute(ch io.ReadWriter) (err error) {
|
||||
if cmd.Source {
|
||||
// download
|
||||
return cmd.serveSource(ch)
|
||||
err = cmd.serveSource(ch)
|
||||
} else {
|
||||
// upload
|
||||
err = cmd.serveSink(ch)
|
||||
}
|
||||
// upload
|
||||
return cmd.serveSink(ch)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (cmd *Command) serveSource(ch io.ReadWriter) error {
|
||||
log.Infof("SCP: serving source")
|
||||
|
||||
r := newReader(ch)
|
||||
|
||||
if err := r.read(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(cmd.Target)
|
||||
if err != nil {
|
||||
log.Infof("failed to stat file: %v", err)
|
||||
return sendError(ch, err.Error())
|
||||
return trace.Wrap(sendError(ch, err))
|
||||
}
|
||||
|
||||
if f.IsDir() && !cmd.Recursive {
|
||||
return sendError(
|
||||
ch, fmt.Sprintf(
|
||||
"%v is not a file, turn recursive mode to copy dirs",
|
||||
cmd.Target))
|
||||
err := trace.Errorf("%v is not a file, turn recursive mode to copy dirs", cmd.Target)
|
||||
return trace.Wrap(sendError(ch, err))
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
if err := cmd.sendDir(r, ch, f, cmd.Target); err != nil {
|
||||
return sendError(ch, err.Error())
|
||||
return trace.Wrap(sendError(ch, err))
|
||||
}
|
||||
} else {
|
||||
if err := cmd.sendFile(r, ch, f, cmd.Target); err != nil {
|
||||
return sendError(ch, err.Error())
|
||||
return trace.Wrap(sendError(ch, err))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,23 +152,24 @@ func (cmd *Command) sendFile(r *reader, ch io.ReadWriter, fi os.FileInfo, path s
|
|||
cmd.AuditLog.EmitAuditEvent(events.SCPEvent, events.EventFields{
|
||||
events.SCPPath: path,
|
||||
events.SCPLengh: fi.Size(),
|
||||
events.LocalAddr: cmd.Conn.LocalAddr().String(),
|
||||
events.RemoteAddr: cmd.Conn.RemoteAddr().String(),
|
||||
events.LocalAddr: cmd.LocalAddr,
|
||||
events.RemoteAddr: cmd.RemoteAddr,
|
||||
events.EventLogin: cmd.User.Username,
|
||||
events.SCPAction: "read",
|
||||
})
|
||||
}
|
||||
|
||||
out := fmt.Sprintf("C%04o %d %s\n", fi.Mode()&os.ModePerm, fi.Size(), fi.Name())
|
||||
log.Infof("sendFile: %v", out)
|
||||
|
||||
_, err := io.WriteString(ch, out)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := r.read(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
log.Infof("sendFile got OK")
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -182,7 +186,7 @@ func (cmd *Command) sendFile(r *reader, ch io.ReadWriter, fi os.FileInfo, path s
|
|||
if err := sendOK(ch); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return r.read()
|
||||
return trace.Wrap(r.read())
|
||||
}
|
||||
|
||||
// serveSink executes file uploading, when a remote server sends file(s)
|
||||
|
@ -219,10 +223,7 @@ func (cmd *Command) serveSink(ch io.ReadWriter) error {
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
if err := cmd.processCommand(ch, &st, b[0], scanner.Text()); err != nil {
|
||||
if e := sendError(ch, err.Error()); e != nil {
|
||||
log.Warningf("error sending error: %v", e)
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
return sendError(ch, err)
|
||||
}
|
||||
if err := sendOK(ch); err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -235,8 +236,7 @@ func (cmd *Command) processCommand(ch io.ReadWriter, st *state, b byte, line str
|
|||
log.Infof("<- %v %v", string(b), line)
|
||||
switch b {
|
||||
case WarnByte:
|
||||
log.Warningf("got warning: %v", line)
|
||||
return nil
|
||||
return trace.Errorf(line)
|
||||
case ErrByte:
|
||||
return trace.Errorf(line)
|
||||
case 'C':
|
||||
|
@ -244,10 +244,11 @@ func (cmd *Command) processCommand(ch io.ReadWriter, st *state, b byte, line str
|
|||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if err := sendOK(ch); err != nil {
|
||||
err = cmd.receiveFile(st, *f, ch)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return cmd.receiveFile(st, *f, ch)
|
||||
return nil
|
||||
case 'D':
|
||||
d, err := ParseNewFile(line)
|
||||
if err != nil {
|
||||
|
@ -269,17 +270,12 @@ func (cmd *Command) processCommand(ch io.ReadWriter, st *state, b byte, line str
|
|||
}
|
||||
|
||||
func (cmd *Command) receiveFile(st *state, fc NewFileCmd, ch io.ReadWriter) error {
|
||||
isDir := func(target string) bool {
|
||||
fi, err := os.Stat(target)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return fi.IsDir()
|
||||
}
|
||||
log.Infof("scp.receiveFile(%v)", cmd.Target)
|
||||
|
||||
// if the dest path is a folder, we should save the file to that folder, but
|
||||
// only if is 'recursive' is set
|
||||
path := cmd.Target
|
||||
if cmd.Recursive || isDir(path) {
|
||||
if cmd.Recursive || utils.IsDir(path) {
|
||||
path = st.makePath(path, fc.Name)
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
|
@ -290,8 +286,8 @@ func (cmd *Command) receiveFile(st *state, fc NewFileCmd, ch io.ReadWriter) erro
|
|||
// log audit event:
|
||||
if cmd.AuditLog != nil {
|
||||
cmd.AuditLog.EmitAuditEvent(events.SCPEvent, events.EventFields{
|
||||
events.LocalAddr: cmd.Conn.LocalAddr().String(),
|
||||
events.RemoteAddr: cmd.Conn.RemoteAddr().String(),
|
||||
events.LocalAddr: cmd.LocalAddr,
|
||||
events.RemoteAddr: cmd.RemoteAddr,
|
||||
events.EventLogin: cmd.User.Username,
|
||||
events.SCPPath: path,
|
||||
events.SCPLengh: fc.Length,
|
||||
|
@ -300,10 +296,17 @@ func (cmd *Command) receiveFile(st *state, fc NewFileCmd, ch io.ReadWriter) erro
|
|||
}
|
||||
|
||||
defer f.Close()
|
||||
n, err := io.CopyN(f, ch, int64(fc.Length))
|
||||
if err != nil {
|
||||
|
||||
if err = sendOK(ch); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
n, err := io.CopyN(f, ch, int64(fc.Length))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if n != int64(fc.Length) {
|
||||
return trace.Errorf("unexpected file copy length: %v", n)
|
||||
}
|
||||
|
@ -339,29 +342,17 @@ func (cmd *Command) receiveDir(st *state, fc NewFileCmd, ch io.ReadWriter) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func IsSCP(cmd string) bool {
|
||||
args := strings.Split(cmd, " ")
|
||||
if len(args) < 1 {
|
||||
return false
|
||||
}
|
||||
_, f := filepath.Split(args[0])
|
||||
return f == "scp"
|
||||
}
|
||||
|
||||
func ParseCommand(arg string, conn ssh.ConnMetadata, alog events.IAuditLog) (*Command, error) {
|
||||
if !IsSCP(arg) {
|
||||
return nil, trace.Errorf("not scp command")
|
||||
}
|
||||
userName := conn.User()
|
||||
args := strings.Split(arg, " ")
|
||||
f := flag.NewFlagSet(args[0], flag.ContinueOnError)
|
||||
|
||||
// get user's home dir (it serves as a default destination)
|
||||
osUser, err := user.Lookup(userName)
|
||||
osUser, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, trace.Errorf("user not found: %s", userName)
|
||||
log.Error(err)
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
cmd := Command{User: osUser, Conn: conn, AuditLog: alog}
|
||||
cmd := Command{User: osUser, AuditLog: alog}
|
||||
|
||||
f.BoolVar(&cmd.Sink, "t", false, "sink mode (data consumer)")
|
||||
f.BoolVar(&cmd.Source, "f", false, "source mode (data producer)")
|
||||
|
@ -380,7 +371,7 @@ func ParseCommand(arg string, conn ssh.ConnMetadata, alog events.IAuditLog) (*Co
|
|||
withSlash := strings.HasSuffix(cmd.Target, slash)
|
||||
if !filepath.IsAbs(cmd.Target) {
|
||||
rootDir := cmd.User.HomeDir
|
||||
if !isDir(rootDir) {
|
||||
if !utils.IsDir(rootDir) {
|
||||
cmd.Target = slash + cmd.Target
|
||||
} else {
|
||||
cmd.Target = filepath.Join(rootDir, cmd.Target)
|
||||
|
@ -396,16 +387,6 @@ func ParseCommand(arg string, conn ssh.ConnMetadata, alog events.IAuditLog) (*Co
|
|||
return &cmd, nil
|
||||
}
|
||||
|
||||
// isDir returns 'true' if a given path points to a valid, existing directory
|
||||
func isDir(dirPath string) bool {
|
||||
fs, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return false
|
||||
}
|
||||
return fs.IsDir()
|
||||
}
|
||||
|
||||
type NewFileCmd struct {
|
||||
Mode int64
|
||||
Length uint64
|
||||
|
@ -463,17 +444,23 @@ func sendOK(ch io.ReadWriter) error {
|
|||
}
|
||||
}
|
||||
|
||||
func sendError(ch io.ReadWriter, message string) error {
|
||||
// sendError gets called during all errors during SCP transmission. It does
|
||||
// logs the error into Teleport log and also writes it back to the SCP client
|
||||
func sendError(ch io.ReadWriter, err error) error {
|
||||
log.Error(err)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
message := err.Error()
|
||||
bytes := make([]byte, 0, len(message)+2)
|
||||
bytes = append(bytes, ErrByte)
|
||||
bytes = append(bytes, message...)
|
||||
bytes = append(bytes, []byte{'\n'}...)
|
||||
_, err := ch.Write(bytes)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
} else {
|
||||
return nil
|
||||
_, writeErr := ch.Write(bytes)
|
||||
if writeErr != nil {
|
||||
log.Error(writeErr)
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
type state struct {
|
||||
|
@ -516,6 +503,9 @@ type reader struct {
|
|||
r io.Reader
|
||||
}
|
||||
|
||||
// read is used to "ask" for response messages after each SCP transmission
|
||||
// it only reads text data until a newline and returns 'nil' for "OK" responses
|
||||
// and errors for everything else
|
||||
func (r *reader) read() error {
|
||||
n, err := r.r.Read(r.b)
|
||||
if err != nil {
|
||||
|
@ -534,11 +524,7 @@ func (r *reader) read() error {
|
|||
if err := r.s.Err(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if r.b[0] == ErrByte {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
log.Warningf("warn: %v", r.s.Text())
|
||||
return nil
|
||||
return trace.Errorf(r.s.Text())
|
||||
}
|
||||
return trace.Errorf("unrecognized command: %#v", r.b)
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ func InitLoggerForTests() {
|
|||
// FatalError is for CLI front-ends: it detects gravitational.Trace debugging
|
||||
// information, sends it to the logger, strips it off and prints a clean message to stderr
|
||||
func FatalError(err error) {
|
||||
fmt.Fprintln(os.Stderr, "ERROR: "+UserMessageFromError(err))
|
||||
fmt.Fprintln(os.Stderr, UserMessageFromError(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
|
@ -30,6 +31,7 @@ func (fs *FileAddrStorage) SetAddresses(addrs []NetAddr) error {
|
|||
}
|
||||
err = ioutil.WriteFile(fs.filePath, bytes, 0666)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return trace.ConvertSystemError(err)
|
||||
}
|
||||
return nil
|
||||
|
@ -39,12 +41,15 @@ func (fs *FileAddrStorage) SetAddresses(addrs []NetAddr) error {
|
|||
func (fs *FileAddrStorage) GetAddresses() ([]NetAddr, error) {
|
||||
bytes, err := ioutil.ReadFile(fs.filePath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
var addrs []NetAddr
|
||||
err = json.Unmarshal(bytes, &addrs)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
if len(bytes) > 0 {
|
||||
err = json.Unmarshal(bytes, &addrs)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
|
|
|
@ -155,12 +155,12 @@ func main() {
|
|||
// operations with authorities
|
||||
auth := app.Command("authorities", "Operations with user and host certificate authorities").Hidden()
|
||||
auth.Flag("type", "authority type, 'user' or 'host'").Default(string(services.UserCA)).StringVar(&cmdAuth.authType)
|
||||
authList := auth.Command("ls", "List trusted user certificate authorities").Hidden()
|
||||
authExport := auth.Command("export", "Export concatenated keys to standard output").Hidden()
|
||||
authList := auth.Command("ls", "List trusted user certificate authorities")
|
||||
authExport := auth.Command("export", "Export concatenated keys to standard output")
|
||||
authExport.Flag("private-keys", "if set, will print private keys").BoolVar(&cmdAuth.exportPrivateKeys)
|
||||
authExport.Flag("fingerprint", "filter authority by fingerprint").StringVar(&cmdAuth.exportAuthorityFingerprint)
|
||||
|
||||
authGenerate := auth.Command("gen", "Generate new OpenSSH keypair").Hidden()
|
||||
authGenerate := auth.Command("gen", "Generate new OpenSSH keypair")
|
||||
authGenerate.Flag("pub-key", "path to the public key to write").Required().StringVar(&cmdAuth.genPubPath)
|
||||
authGenerate.Flag("priv-key", "path to the private key to write").Required().StringVar(&cmdAuth.genPrivPath)
|
||||
|
||||
|
|
|
@ -21,11 +21,14 @@ import (
|
|||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/teleport/lib/config"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/service"
|
||||
"github.com/gravitational/teleport/lib/sshutils/scp"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
@ -48,18 +51,20 @@ func run(cmdlineArgs []string, testRun bool) (executedCommand string, conf *serv
|
|||
|
||||
// define global flags:
|
||||
var ccf config.CommandLineFlags
|
||||
app.Flag("debug", "Enable verbose logging to stderr").
|
||||
Short('d').
|
||||
BoolVar(&ccf.Debug)
|
||||
var scpCommand scp.Command
|
||||
|
||||
// define commands:
|
||||
start := app.Command("start", "Starts the Teleport service.")
|
||||
status := app.Command("status", "Print the status of the current SSH session.")
|
||||
dump := app.Command("configure", "Print the sample config file into stdout.")
|
||||
ver := app.Command("version", "Print the version.")
|
||||
scpc := app.Command("scp", "server-side implementation of scp").Hidden()
|
||||
app.HelpFlag.Short('h')
|
||||
|
||||
// define start flags:
|
||||
start.Flag("debug", "Enable verbose logging to stderr").
|
||||
Short('d').
|
||||
BoolVar(&ccf.Debug)
|
||||
start.Flag("roles",
|
||||
fmt.Sprintf("Comma-separated list of roles to start with [%s]", strings.Join(defaults.StartRoles, ","))).
|
||||
Short('r').
|
||||
|
@ -95,6 +100,17 @@ func run(cmdlineArgs []string, testRun bool) (executedCommand string, conf *serv
|
|||
// define start's usage info (we use kingpin's "alias" field for this)
|
||||
start.Alias(usageNotes + usageExamples)
|
||||
|
||||
// define a hidden 'scp' command (it implements server-side implementation of handling
|
||||
// 'scp' requests)
|
||||
scpc.Flag("t", "sink mode (data consumer)").Short('t').Default("false").BoolVar(&scpCommand.Sink)
|
||||
scpc.Flag("f", "source mode (data producer)").Short('f').Default("false").BoolVar(&scpCommand.Source)
|
||||
scpc.Flag("v", "verbose mode").Default("false").Short('v').BoolVar(&scpCommand.Verbose)
|
||||
scpc.Flag("d", "target is dir and must exist").Short('d').Default("false").BoolVar(&scpCommand.TargetIsDir)
|
||||
scpc.Flag("r", "recursive mode").Default("false").Short('r').BoolVar(&scpCommand.Recursive)
|
||||
scpc.Flag("remote-addr", "address of the remote client").StringVar(&scpCommand.RemoteAddr)
|
||||
scpc.Flag("local-addr", "local address which accepted the request").StringVar(&scpCommand.LocalAddr)
|
||||
scpc.Arg("target", "").StringVar(&scpCommand.Target)
|
||||
|
||||
// parse CLI commands+flags:
|
||||
command, err := app.Parse(cmdlineArgs)
|
||||
if err != nil {
|
||||
|
@ -123,6 +139,8 @@ func run(cmdlineArgs []string, testRun bool) (executedCommand string, conf *serv
|
|||
if !testRun {
|
||||
err = onStart(conf)
|
||||
}
|
||||
case scpc.FullCommand():
|
||||
err = onSCP(&scpCommand)
|
||||
case status.FullCommand():
|
||||
err = onStatus()
|
||||
case dump.FullCommand():
|
||||
|
@ -180,6 +198,49 @@ func onConfigDump() {
|
|||
fmt.Printf("%s\n%s\n", sampleConfComment, sfc.DebugDumpToYAML())
|
||||
}
|
||||
|
||||
// onSCP implements handling of 'scp' requests on the server side. When the teleport SSH daemon
|
||||
// receives an SSH "scp" request, it launches itself with 'scp' flag under the requested
|
||||
// user's privileges
|
||||
//
|
||||
// This is the entry point of "teleport scp" call (the parent process is the teleport daemon)
|
||||
func onSCP(cmd *scp.Command) (err error) {
|
||||
// get user's home dir (it serves as a default destination)
|
||||
cmd.User, err = user.Current()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// see if the target is absolute. if not, use user's homedir to make
|
||||
// it absolute (and if the user doesn't have a homedir, use "/")
|
||||
slash := string(filepath.Separator)
|
||||
withSlash := strings.HasSuffix(cmd.Target, slash)
|
||||
if !filepath.IsAbs(cmd.Target) {
|
||||
rootDir := cmd.User.HomeDir
|
||||
if !utils.IsDir(rootDir) {
|
||||
cmd.Target = slash + cmd.Target
|
||||
} else {
|
||||
cmd.Target = filepath.Join(rootDir, cmd.Target)
|
||||
if withSlash {
|
||||
cmd.Target = cmd.Target + slash
|
||||
}
|
||||
}
|
||||
}
|
||||
if !cmd.Source && !cmd.Sink {
|
||||
return trace.Errorf("remote mode is not supported")
|
||||
}
|
||||
return trace.Wrap(cmd.Execute(&StdReadWriter{}))
|
||||
}
|
||||
|
||||
type StdReadWriter struct {
|
||||
}
|
||||
|
||||
func (rw *StdReadWriter) Read(b []byte) (int, error) {
|
||||
return os.Stdin.Read(b)
|
||||
}
|
||||
|
||||
func (rw *StdReadWriter) Write(b []byte) (int, error) {
|
||||
return os.Stdout.Write(b)
|
||||
}
|
||||
|
||||
// onVersion is the handler for "version"
|
||||
func onVersion() {
|
||||
utils.PrintVersion()
|
||||
|
|
|
@ -242,9 +242,13 @@ func onSSH(cf *CLIConf) {
|
|||
if err != nil {
|
||||
utils.FatalError(err)
|
||||
}
|
||||
|
||||
if err = tc.SSH(cf.RemoteCommand, cf.LocalExec, nil); err != nil {
|
||||
utils.FatalError(err)
|
||||
// exit with the same exit status as the failed command:
|
||||
if tc.ExitStatus != 0 {
|
||||
os.Exit(tc.ExitStatus)
|
||||
} else {
|
||||
utils.FatalError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,7 +274,12 @@ func onSCP(cf *CLIConf) {
|
|||
utils.FatalError(err)
|
||||
}
|
||||
if err := tc.SCP(cf.CopySpec, int(cf.NodePort), cf.RecursiveCopy); err != nil {
|
||||
utils.FatalError(err)
|
||||
// exit with the same exit status as the failed command:
|
||||
if tc.ExitStatus != 0 {
|
||||
os.Exit(tc.ExitStatus)
|
||||
} else {
|
||||
utils.FatalError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,7 +354,9 @@ func makeClient(cf *CLIConf) (tc *client.TeleportClient, err error) {
|
|||
|
||||
// prep client config:
|
||||
c := &client.Config{
|
||||
Output: os.Stdout,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Stdin: os.Stdin,
|
||||
Username: cf.Username,
|
||||
ProxyHost: cf.Proxy,
|
||||
Host: cf.UserHost,
|
||||
|
|
2
vendor/github.com/gravitational/trace/errors.go
generated
vendored
2
vendor/github.com/gravitational/trace/errors.go
generated
vendored
|
@ -242,7 +242,7 @@ func ConvertSystemError(err error) error {
|
|||
switch realErr := innerError.(type) {
|
||||
case *net.OpError:
|
||||
return wrap(&ConnectionProblemError{
|
||||
Message: fmt.Sprintf("failed to connect to server %v", realErr.Addr),
|
||||
Message: fmt.Sprintf("failed to connecting to %v", realErr.Addr),
|
||||
Err: realErr}, 2)
|
||||
case *os.PathError:
|
||||
return wrap(&AccessDeniedError{
|
||||
|
|
Loading…
Reference in a new issue