17119: Merge branch 'master' into 17119-add-filter-groups
[arvados.git] / lib / crunchrun / container_gateway.go
index 3764a8a439690d7e94da59eaa8e686a70a18598c..1a87b8c4f71d1db95b62dc6c572107f9e1e815d8 100644 (file)
@@ -40,18 +40,17 @@ type Gateway struct {
        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() == "_" {
@@ -60,9 +59,8 @@ func (gw *Gateway) Start() error {
                                                "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)
@@ -126,8 +124,9 @@ func (gw *Gateway) Start() error {
        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).
 //
@@ -140,10 +139,12 @@ func (gw *Gateway) Start() error {
 // 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
@@ -256,15 +257,18 @@ func (gw *Gateway) handleSSH(w http.ResponseWriter, req *http.Request) {
                                                }
                                                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
                                                }