17170: Specify login username. Improve logging.
authorTom Clegg <tom@curii.com>
Tue, 12 Jan 2021 05:30:45 +0000 (00:30 -0500)
committerTom Clegg <tom@curii.com>
Tue, 12 Jan 2021 05:32:03 +0000 (00:32 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

cmd/arvados-client/container_gateway.go
lib/controller/localdb/container_gateway.go
lib/controller/rpc/conn.go
lib/crunchrun/container_gateway.go
sdk/go/arvados/api.go
sdk/go/arvados/container_gateway.go

index 46fa6932b33e67938145274af46fa7f07b5ad309..d15b9d7b5e87c3b086b3e7f0083e5d54a3c849dd 100644 (file)
@@ -29,7 +29,7 @@ func (shellCommand) RunCommand(prog string, args []string, stdin io.Reader, stdo
        f.Usage = func() {
                fmt.Print(stderr, prog+`: open an interactive shell on a running container.
 
-Usage: `+prog+` [options] container-uuid [ssh-options] [remote-command [args...]]
+Usage: `+prog+` [options] [username@]container-uuid [ssh-options] [remote-command [args...]]
 
 Options:
 `)
@@ -47,7 +47,10 @@ Options:
                f.Usage()
                return 2
        }
-       targetUUID := f.Args()[0]
+       target := f.Args()[0]
+       if !strings.Contains(target, "@") {
+               target = "root@" + target
+       }
        sshargs := f.Args()[1:]
 
        selfbin, err := os.Readlink("/proc/self/exe")
@@ -56,9 +59,9 @@ Options:
                return 2
        }
        sshargs = append([]string{
-               "-o", "ProxyCommand " + selfbin + " connect-ssh -detach-keys='" + strings.Replace(*detachKeys, "'", "'\\''", -1) + "' " + targetUUID,
+               "-o", "ProxyCommand " + selfbin + " connect-ssh -detach-keys=" + shellescape(*detachKeys) + " " + shellescape(target),
                "-o", "StrictHostKeyChecking no",
-               "root@" + targetUUID},
+               target},
                sshargs...)
        cmd := exec.Command("ssh", sshargs...)
        cmd.Stdin = stdin
@@ -91,7 +94,7 @@ func (connectSSHCommand) RunCommand(prog string, args []string, stdin io.Reader,
        f.Usage = func() {
                fmt.Fprint(stderr, prog+`: connect to the gateway service for a running container.
 
-Usage: `+prog+` [options] container-uuid
+Usage: `+prog+` [options] [username@]container-uuid
 
 Options:
 `)
@@ -107,6 +110,11 @@ Options:
                return 2
        }
        targetUUID := f.Args()[0]
+       loginUsername := "root"
+       if i := strings.Index(targetUUID, "@"); i >= 0 {
+               loginUsername = targetUUID[:i]
+               targetUUID = targetUUID[i+1:]
+       }
        insecure := os.Getenv("ARVADOS_API_HOST_INSECURE")
        rpcconn := rpc.NewConn("",
                &url.URL{
@@ -130,8 +138,9 @@ Options:
        //      targetUUID = cr.ContainerUUID
        // }
        sshconn, err := rpcconn.ContainerSSH(context.TODO(), arvados.ContainerSSHOptions{
-               UUID:       targetUUID,
-               DetachKeys: *detachKeys,
+               UUID:          targetUUID,
+               DetachKeys:    *detachKeys,
+               LoginUsername: loginUsername,
        })
        if err != nil {
                fmt.Fprintln(stderr, err)
@@ -157,3 +166,7 @@ Options:
        <-ctx.Done()
        return 0
 }
+
+func shellescape(s string) string {
+       return "'" + strings.Replace(s, "'", "'\\''", -1) + "'"
+}
index 01b4065f9ba9b8cba13b798505a85232cbf09051..31d44e5e0d88e4f1cab146f28df5384f5c0ff15c 100644 (file)
@@ -17,6 +17,7 @@ import (
        "strings"
 
        "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
 )
 
@@ -60,6 +61,7 @@ func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOpt
        bufw.WriteString("X-Arvados-Target-Uuid: " + opts.UUID + "\r\n")
        bufw.WriteString("X-Arvados-Authorization: " + auth + "\r\n")
        bufw.WriteString("X-Arvados-Detach-Keys: " + opts.DetachKeys + "\r\n")
+       bufw.WriteString("X-Arvados-Login-Username: " + opts.LoginUsername + "\r\n")
        bufw.WriteString("\r\n")
        bufw.Flush()
        resp, err := http.ReadResponse(bufr, &http.Request{Method: "GET"})
@@ -76,5 +78,6 @@ func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOpt
        }
        sshconn.Conn = netconn
        sshconn.Bufrw = &bufio.ReadWriter{Reader: bufr, Writer: bufw}
+       sshconn.Logger = ctxlog.FromContext(ctx)
        return
 }
index e80542e3e1b43598d61aa06132ebaca2be6f44ba..75603bb59d50cea0f8717ae63265a63f73838390 100644 (file)
@@ -305,7 +305,10 @@ func (conn *Conn) ContainerSSH(ctx context.Context, options arvados.ContainerSSH
                netconn.Close()
                return
        }
-       u.RawQuery = url.Values{"detach_keys": {options.DetachKeys}}.Encode()
+       u.RawQuery = url.Values{
+               "detach_keys":    {options.DetachKeys},
+               "login_username": {options.LoginUsername},
+       }.Encode()
        tokens, err := conn.tokenProvider(ctx)
        if err != nil {
                netconn.Close()
index e55868ac33e9dbff093945dc5e713c75273c0c8b..92c11383f42dbb732dacc740a68a55355556f799 100644 (file)
@@ -29,21 +29,21 @@ func (runner *ContainerRunner) startGatewayServer() error {
        runner.gatewaySSHConfig = &ssh.ServerConfig{
                NoClientAuth: true,
                PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
-                       if c.User() == "root" {
+                       if c.User() == "_" {
                                return nil, nil
                        } else {
-                               return nil, fmt.Errorf("unimplemented: cannot log in as non-root user %q", 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() == "root" {
+                       if c.User() == "_" {
                                return &ssh.Permissions{
                                        Extensions: map[string]string{
                                                "pubkey-fp": ssh.FingerprintSHA256(pubKey),
                                        },
                                }, nil
                        } else {
-                               return nil, fmt.Errorf("unimplemented: cannot log in as non-root user %q", c.User())
+                               return nil, fmt.Errorf("cannot specify user %q via ssh client", c.User())
                        }
                },
        }
@@ -129,6 +129,10 @@ func (runner *ContainerRunner) handleSSH(w http.ResponseWriter, req *http.Reques
                return
        }
        detachKeys := req.Header.Get("X-Arvados-Detach-Keys")
+       username := req.Header.Get("X-Arvados-Login-Username")
+       if username == "" {
+               username = "root"
+       }
        hj, ok := w.(http.Hijacker)
        if !ok {
                http.Error(w, "ResponseWriter does not support connection upgrade", http.StatusInternalServerError)
@@ -196,7 +200,7 @@ func (runner *ContainerRunner) handleSSH(w http.ResponseWriter, req *http.Reques
                                                execargs = []string{"/bin/bash", "-login"}
                                        }
                                        go func() {
-                                               cmd := exec.CommandContext(ctx, "docker", "exec", "-i", "--detach-keys="+detachKeys)
+                                               cmd := exec.CommandContext(ctx, "docker", "exec", "-i", "--detach-keys="+detachKeys, "--user="+username)
                                                cmd.Stdin = ch
                                                cmd.Stdout = ch
                                                cmd.Stderr = ch.Stderr()
index fa4471804809fc60b2c233b8f8175d308a6ae369..e8baa01b40619c57f36c64748d7525e92f075922 100644 (file)
@@ -9,6 +9,8 @@ import (
        "context"
        "encoding/json"
        "net"
+
+       "github.com/sirupsen/logrus"
 )
 
 type APIEndpoint struct {
@@ -64,13 +66,15 @@ var (
 )
 
 type ContainerSSHOptions struct {
-       UUID       string `json:"uuid"`
-       DetachKeys string `json:"detach_keys"`
+       UUID          string `json:"uuid"`
+       DetachKeys    string `json:"detach_keys"`
+       LoginUsername string `json:"login_username"`
 }
 
 type ContainerSSHConnection struct {
-       Conn  net.Conn          `json:"-"`
-       Bufrw *bufio.ReadWriter `json:"-"`
+       Conn   net.Conn           `json:"-"`
+       Bufrw  *bufio.ReadWriter  `json:"-"`
+       Logger logrus.FieldLogger `json:"-"`
 }
 
 type GetOptions struct {
index 07f8c0793caacf0ae728e798a4e195deba1e59a7..00c98d572ea010e2db43ca1c62bfa0eb341b1e7d 100644 (file)
@@ -8,8 +8,10 @@ import (
        "context"
        "io"
        "net/http"
+       "sync"
 
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "github.com/sirupsen/logrus"
 )
 
 func (sshconn ContainerSSHConnection) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@@ -28,26 +30,45 @@ func (sshconn ContainerSSHConnection) ServeHTTP(w http.ResponseWriter, req *http
        }
        defer conn.Close()
 
+       var bytesIn, bytesOut int64
+       var wg sync.WaitGroup
        ctx, cancel := context.WithCancel(context.Background())
+       wg.Add(1)
        go func() {
+               defer wg.Done()
                defer cancel()
-               _, err := io.CopyN(conn, sshconn.Bufrw, int64(sshconn.Bufrw.Reader.Buffered()))
+               n, err := io.CopyN(conn, sshconn.Bufrw, int64(sshconn.Bufrw.Reader.Buffered()))
+               bytesOut += n
                if err == nil {
-                       _, err = io.Copy(conn, sshconn.Conn)
+                       n, err = io.Copy(conn, sshconn.Conn)
+                       bytesOut += n
                }
                if err != nil {
                        ctxlog.FromContext(req.Context()).WithError(err).Error("error copying downstream")
                }
        }()
+       wg.Add(1)
        go func() {
+               defer wg.Done()
                defer cancel()
-               _, err := io.CopyN(sshconn.Conn, bufrw, int64(bufrw.Reader.Buffered()))
+               n, err := io.CopyN(sshconn.Conn, bufrw, int64(bufrw.Reader.Buffered()))
+               bytesIn += n
                if err == nil {
-                       _, err = io.Copy(sshconn.Conn, conn)
+                       n, err = io.Copy(sshconn.Conn, conn)
+                       bytesIn += n
                }
                if err != nil {
                        ctxlog.FromContext(req.Context()).WithError(err).Error("error copying upstream")
                }
        }()
        <-ctx.Done()
+       if sshconn.Logger != nil {
+               go func() {
+                       wg.Wait()
+                       sshconn.Logger.WithFields(logrus.Fields{
+                               "bytesIn":  bytesIn,
+                               "bytesOut": bytesOut,
+                       }).Info("closed connection")
+               }()
+       }
 }