17170: Improve error messages and command help text.
[arvados.git] / lib / controller / localdb / container_gateway.go
index dd296c569cb3db9e1edd3ce8039d5c5170593c8b..3b40eccaff68c138e498b9ec497f5f704e249f64 100644 (file)
@@ -18,6 +18,7 @@ import (
        "strings"
 
        "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/auth"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
 )
@@ -29,12 +30,48 @@ import (
 // If the returned error is nil, the caller is responsible for closing
 // sshconn.Conn.
 func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOptions) (sshconn arvados.ContainerSSHConnection, err error) {
+       user, err := conn.railsProxy.UserGetCurrent(ctx, arvados.GetOptions{})
+       if err != nil {
+               return
+       }
        ctr, err := conn.railsProxy.ContainerGet(ctx, arvados.GetOptions{UUID: opts.UUID})
        if err != nil {
                return
        }
-       if ctr.GatewayAddress == "" || ctr.State != arvados.ContainerStateRunning {
-               err = httpserver.ErrorWithStatus(fmt.Errorf("gateway is not available, container is %s", strings.ToLower(string(ctr.State))), http.StatusBadGateway)
+       ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
+       if !user.IsAdmin || !conn.cluster.Containers.ShellAccess.Admin {
+               if !conn.cluster.Containers.ShellAccess.User {
+                       err = httpserver.ErrorWithStatus(errors.New("shell access is disabled in config"), http.StatusServiceUnavailable)
+                       return
+               }
+               var crs arvados.ContainerRequestList
+               crs, err = conn.railsProxy.ContainerRequestList(ctxRoot, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"container_uuid", "=", opts.UUID}}})
+               if err != nil {
+                       return
+               }
+               for _, cr := range crs.Items {
+                       if cr.ModifiedByUserUUID != user.UUID {
+                               err = httpserver.ErrorWithStatus(errors.New("permission denied: container is associated with requests submitted by other users"), http.StatusForbidden)
+                               return
+                       }
+               }
+               if crs.ItemsAvailable != len(crs.Items) {
+                       err = httpserver.ErrorWithStatus(errors.New("incomplete response while checking permission"), http.StatusInternalServerError)
+                       return
+               }
+       }
+
+       switch ctr.State {
+       case arvados.ContainerStateQueued, arvados.ContainerStateLocked:
+               err = httpserver.ErrorWithStatus(fmt.Errorf("container is not running yet (state is %q)", ctr.State), http.StatusServiceUnavailable)
+               return
+       case arvados.ContainerStateRunning:
+               if ctr.GatewayAddress == "" {
+                       err = httpserver.ErrorWithStatus(errors.New("container is running but gateway is not available -- installation problem or feature not supported"), http.StatusServiceUnavailable)
+                       return
+               }
+       default:
+               err = httpserver.ErrorWithStatus(fmt.Errorf("container has ended (state is %q)", ctr.State), http.StatusGone)
                return
        }
        // crunch-run uses a self-signed / unverifiable TLS
@@ -63,7 +100,7 @@ func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOpt
                                return errors.New("no certificate received, cannot compute authorization header")
                        }
                        h := hmac.New(sha256.New, []byte(conn.cluster.SystemRootToken))
-                       fmt.Fprint(h, "%s", opts.UUID)
+                       fmt.Fprint(h, opts.UUID)
                        authKey := fmt.Sprintf("%x", h.Sum(nil))
                        h = hmac.New(sha256.New, []byte(authKey))
                        h.Write(rawCerts[0])
@@ -75,6 +112,7 @@ func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOpt
                },
        })
        if err != nil {
+               err = httpserver.ErrorWithStatus(err, http.StatusBadGateway)
                return
        }
        if respondAuth == "" {
@@ -115,6 +153,20 @@ func (conn *Conn) ContainerSSH(ctx context.Context, opts arvados.ContainerSSHOpt
                netconn.Close()
                return
        }
+
+       if !ctr.InteractiveSessionStarted {
+               _, err = conn.railsProxy.ContainerUpdate(ctxRoot, arvados.UpdateOptions{
+                       UUID: opts.UUID,
+                       Attrs: map[string]interface{}{
+                               "interactive_session_started": true,
+                       },
+               })
+               if err != nil {
+                       netconn.Close()
+                       return
+               }
+       }
+
        sshconn.Conn = netconn
        sshconn.Bufrw = &bufio.ReadWriter{Reader: bufr, Writer: bufw}
        sshconn.Logger = ctxlog.FromContext(ctx)