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:
`)
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")
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
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:
`)
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{
// 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)
<-ctx.Done()
return 0
}
+
+func shellescape(s string) string {
+ return "'" + strings.Replace(s, "'", "'\\''", -1) + "'"
+}
"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"
)
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"})
}
sshconn.Conn = netconn
sshconn.Bufrw = &bufio.ReadWriter{Reader: bufr, Writer: bufw}
+ sshconn.Logger = ctxlog.FromContext(ctx)
return
}
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()
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())
}
},
}
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)
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()
"context"
"encoding/json"
"net"
+
+ "github.com/sirupsen/logrus"
)
type APIEndpoint struct {
)
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 {
"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) {
}
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")
+ }()
+ }
}