Merge pull request #422 from gravitational/ev/scp

SCP/Exec improvements
This commit is contained in:
Alexander Klizhentas 2016-05-27 10:46:14 -07:00
commit f6aa265318
19 changed files with 366 additions and 306 deletions

View file

@ -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
```

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)
}

View file

@ -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))

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)

View file

@ -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()

View file

@ -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,

View file

@ -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{