respondAuth string
}
-// startGatewayServer starts an http server that allows authenticated
-// clients to open an interactive "docker exec" session and (in
-// future) connect to tcp ports inside the docker container.
+// Start starts an http server that allows authenticated clients to open an
+// interactive "docker exec" session and (in future) connect to tcp ports
+// inside the docker container.
func (gw *Gateway) Start() error {
gw.sshConfig = ssh.ServerConfig{
NoClientAuth: true,
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if c.User() == "_" {
return nil, nil
- } else {
- return nil, fmt.Errorf("cannot specify user %q via ssh client", c.User())
}
+ return nil, fmt.Errorf("cannot specify user %q via ssh client", c.User())
},
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if c.User() == "_" {
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
- } else {
- return nil, fmt.Errorf("cannot specify user %q via ssh client", c.User())
}
+ return nil, fmt.Errorf("cannot specify user %q via ssh client", c.User())
},
}
pvt, err := rsa.GenerateKey(rand.Reader, 2048)
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
}
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
}