diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 7d75bf9a0ac55..dff8800b7d56d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -85,7 +85,7 @@ }, { "ImportPath": "github.com/gravitational/trace", - "Rev": "cd3a2b1d2fe8fe085c02d11c0f6ce46bca45b980" + "Rev": "0fd0db9ea941edb81e90278f4eb8f29f5615637e" }, { "ImportPath": "github.com/jonboulle/clockwork", diff --git a/errors.go b/errors.go index 09848d25a2bdf..ceadfc2a0db67 100644 --- a/errors.go +++ b/errors.go @@ -19,6 +19,7 @@ package teleport import ( "fmt" "net" + "os" "syscall" "github.com/gravitational/trace" @@ -304,19 +305,23 @@ func IsAccessDenied(e error) bool { return ok } -// ConvertConnectionProblem converts system error to connection problem -// if applicable -func ConvertConnectionProblem(err error) error { +// ConvertSystemError converts system error to appropriate teleport error +// if it is possible, otherwise, returns original error +func ConvertSystemError(err error) error { innerError := err if terr, ok := err.(trace.Error); ok { innerError = terr.OrigError() } - neterr, ok := err.(*net.OpError) - if !ok { + switch realErr := innerError.(type) { + case *net.OpError: + return ConnectionProblem( + fmt.Sprintf("failed to connect to server %v", realErr.Addr), realErr) + case *os.PathError: + return AccessDenied( + fmt.Sprintf("failed to execute command %v error: %v", realErr.Path, realErr.Err)) + default: return err } - return ConnectionProblem( - fmt.Sprintf("failed to connect to server %v", neterr.Addr), innerError) } // ConnectionProblem returns ConnectionProblem @@ -336,7 +341,7 @@ type ConnectionProblemError struct { // Error is debug - friendly error message func (c *ConnectionProblemError) Error() string { - return fmt.Sprintf("connection problem: %v, %v", c.Message, c.Err.Error()) + return fmt.Sprintf("%v: %v", c.Message, c.Err.Error()) } // IsConnectionProblemError indicates that this error is of ConnectionProblem diff --git a/lib/client/client.go b/lib/client/client.go index cbf1bf0d534b4..52b91033628d9 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -198,7 +198,7 @@ func (proxy *ProxyClient) ConnectToNode( buf := &bytes.Buffer{} io.Copy(buf, proxyErr) if buf.String() != "" { - fmt.Println("ERROR: " + buf.String() + "\n") + fmt.Println("ERROR: " + buf.String()) } } err = proxySession.RequestSubsystem(fmt.Sprintf("proxy:%v", nodeAddress)) @@ -313,6 +313,19 @@ func (client *NodeClient) Shell(width, height int, sessionID string) (io.ReadWri return nil, trace.Wrap(err) } + stderr, err := session.StderrPipe() + if err != nil { + return nil, trace.Wrap(err) + } + + go func() { + buf := &bytes.Buffer{} + io.Copy(buf, stderr) + if buf.String() != "" { + fmt.Println("ERROR: " + buf.String()) + } + }() + err = session.Shell() if err != nil { return nil, trace.Wrap(err) diff --git a/lib/srv/exec.go b/lib/srv/exec.go index 60ffa3dcb8a3f..d2f69df3829f1 100644 --- a/lib/srv/exec.go +++ b/lib/srv/exec.go @@ -24,7 +24,9 @@ import ( "os/exec" "syscall" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/trace" "golang.org/x/crypto/ssh" ) @@ -90,7 +92,7 @@ func (e *execFn) start(sconn *ssh.ServerConn, shell string, ch ssh.Channel) (*ex if err := e.cmd.Start(); err != nil { e.ctx.Warningf("%v start failure err: %v", e, err) - return e.collectStatus(e.cmd, err) + return e.collectStatus(e.cmd, teleport.ConvertSystemError(err)) } e.ctx.Infof("%v started", e) return nil, nil diff --git a/lib/srv/proxy.go b/lib/srv/proxy.go index d12224605f12c..0a6fffb436d70 100644 --- a/lib/srv/proxy.go +++ b/lib/srv/proxy.go @@ -103,7 +103,7 @@ func (t *proxySubsys) start(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh.Requ // may not be actually DNS resolvable conn, err := remoteSrv.DialServer(serverAddr) if err != nil { - return trace.Wrap(teleport.ConvertConnectionProblem(err)) + return trace.Wrap(teleport.ConvertSystemError(err)) } go func() { var err error diff --git a/lib/srv/sess.go b/lib/srv/sess.go index 85c5a1ce140fd..50e9383aba30e 100644 --- a/lib/srv/sess.go +++ b/lib/srv/sess.go @@ -53,6 +53,7 @@ func (s *sessionRegistry) newShell(sid string, sconn *ssh.ServerConn, ch ssh.Cha return trace.Wrap(err) } if err := sess.start(sconn, ch, ctx); err != nil { + defer sess.Close() return trace.Wrap(err) } s.addSession(sess) @@ -306,8 +307,8 @@ func (s *session) start(sconn *ssh.ServerConn, ch ssh.Channel, ctx *ctx) error { } if err := s.term.run(cmd); err != nil { - p.ctx.Infof("failed to start shell: %v", err) - return trace.Wrap(err) + ctx.Infof("shell command failed: %v", err) + return teleport.ConvertSystemError(trace.Wrap(err)) } p.ctx.Infof("starting shell input/output streaming") diff --git a/lib/utils/addr.go b/lib/utils/addr.go index 988a2a5f62ab1..b8eaa140cdc98 100644 --- a/lib/utils/addr.go +++ b/lib/utils/addr.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + package utils import ( diff --git a/lib/utils/cli.go b/lib/utils/cli.go index b167df4e746da..07d1800842ae2 100644 --- a/lib/utils/cli.go +++ b/lib/utils/cli.go @@ -29,7 +29,7 @@ import ( logrusSyslog "github.com/Sirupsen/logrus/hooks/syslog" ) -// CLI tools by default log into syslog, not stderr +// InitLoggerCLI tools by default log into syslog, not stderr func InitLoggerCLI() { log.SetLevel(log.InfoLevel) // clear existing hooks: @@ -47,7 +47,7 @@ func InitLoggerCLI() { } } -// Configures the logger to dump everything to stderr +// InitLoggerDebug configures the logger to dump everything to stderr func InitLoggerDebug() { // clear existing hooks: log.StandardLogger().Hooks = make(log.LevelHooks) diff --git a/lib/utils/utils.go b/lib/utils/utils.go index caf9d413639b5..6d3b6bebd79aa 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -115,6 +115,12 @@ func IsHandshakeFailedError(err error) bool { return strings.Contains(err.Error(), "ssh: handshake failed") } +// IsShellFailedError specifies whether this error indicates +// failed attempt to start shell +func IsShellFailedError(err error) bool { + return strings.Contains(err.Error(), "ssh: cound not start shell") +} + // PortList is a list of TCP port type PortList []string diff --git a/lib/web/web_test.go b/lib/web/web_test.go index f85110ef622c4..d5fde00175ed1 100644 --- a/lib/web/web_test.go +++ b/lib/web/web_test.go @@ -395,9 +395,8 @@ func (s *WebSuite) TestWebSessionsLogout(c *C) { return redirectErr } re, err = pack.clt.Get(pack.clt.Endpoint("webapi", "logout"), url.Values{}) - orig, ok := err.(*trace.TraceErr) - c.Assert(ok, Equals, true) - c.Assert(orig.OrigError(), Equals, redirectErr) + c.Assert(err, NotNil) + c.Assert(err.Error(), Matches, ".*attempted redirect.*") // subsequent requests trying to use this session will fail re, err = pack.clt.Get(pack.clt.Endpoint("webapi", "sites"), url.Values{}) diff --git a/vendor/github.com/gravitational/trace/trace.go b/vendor/github.com/gravitational/trace/trace.go index 6e901503e7a17..1086aa74987f9 100644 --- a/vendor/github.com/gravitational/trace/trace.go +++ b/vendor/github.com/gravitational/trace/trace.go @@ -153,9 +153,25 @@ func (e *TraceErr) Error() string { // OrigError returns original wrapped error func (e *TraceErr) OrigError() error { - return e.error + err := e.error + // this is not an endless loop because I'm being + // paranoid, this is a safe protection against endless + // loops + for i := 0; i < maxHops; i++ { + newerr, ok := err.(Error) + if !ok { + break + } + if newerr.OrigError() != err { + err = newerr.OrigError() + } + } + return err } +// maxHops is a max supported nested depth for errors +const maxHops = 50 + // Error is an interface that helps to adapt usage of trace in the code // When applications define new error types, they can implement the interface // So error handlers can use OrigError() to retrieve error from the wrapper