return nil
}
-// handleSSH connects to an SSH server that runs commands as root in
-// the container. The tunnel itself can only be created by an
+// handleSSH connects to an SSH server that allows the caller to run
+// interactive commands as root (or any other desired user) inside the
+// container. The tunnel itself can only be created by an
// authenticated caller, so the SSH server itself is wide open (any
// password or key will be accepted).
//
// hmac(AuthSecret,certfingerprint) (this prevents other containers
// and shell nodes from connecting directly)
//
-// Optional header:
+// Optional headers:
//
-// X-Arvados-Detach-Keys: argument to "docker attach --detach-keys",
+// X-Arvados-Detach-Keys: argument to "docker exec --detach-keys",
// e.g., "ctrl-p,ctrl-q"
+// X-Arvados-Login-Username: argument to "docker exec --user": account
+// used to run command(s) inside the container.
func (gw *Gateway) handleSSH(w http.ResponseWriter, req *http.Request) {
// In future we'll handle browser traffic too, but for now the
// only traffic we expect is an SSH tunnel from
go ssh.DiscardRequests(reqs)
for newch := range newchans {
if newch.ChannelType() != "session" {
- newch.Reject(ssh.UnknownChannelType, "unknown channel type")
+ newch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unsupported channel type %q", newch.ChannelType()))
continue
}
ch, reqs, err := newch.Accept()
}
cmd.Env = append(os.Environ(), termEnv...)
err := cmd.Run()
- errClose := ch.CloseWrite()
var resp struct {
Status uint32
}
- if err, ok := err.(*exec.ExitError); ok {
- if status, ok := err.Sys().(syscall.WaitStatus); ok {
+ if exiterr, ok := err.(*exec.ExitError); ok {
+ if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
resp.Status = uint32(status.ExitStatus())
}
+ } else if err != nil {
+ // Propagate errors like `exec: "docker": executable file not found in $PATH`
+ fmt.Fprintln(ch.Stderr(), err)
}
+ errClose := ch.CloseWrite()
if resp.Status == 0 && (err != nil || errClose != nil) {
resp.Status = 1
}