1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
17 "git.arvados.org/arvados.git/lib/config"
18 "git.arvados.org/arvados.git/lib/crunchrun"
19 "git.arvados.org/arvados.git/sdk/go/arvados"
20 "git.arvados.org/arvados.git/sdk/go/arvadostest"
21 "git.arvados.org/arvados.git/sdk/go/auth"
22 "git.arvados.org/arvados.git/sdk/go/ctxlog"
23 "golang.org/x/crypto/ssh"
24 check "gopkg.in/check.v1"
27 var _ = check.Suite(&ContainerGatewaySuite{})
29 type ContainerGatewaySuite struct {
30 cluster *arvados.Cluster
37 func (s *ContainerGatewaySuite) TearDownSuite(c *check.C) {
38 // Undo any changes/additions to the user database so they
39 // don't affect subsequent tests.
40 arvadostest.ResetEnv()
41 c.Check(arvados.NewClientFromEnv().RequestAndDecode(nil, "POST", "database/reset", nil, nil), check.IsNil)
44 func (s *ContainerGatewaySuite) SetUpSuite(c *check.C) {
45 cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
46 c.Assert(err, check.IsNil)
47 s.cluster, err = cfg.GetCluster("")
48 c.Assert(err, check.IsNil)
49 s.localdb = NewConn(s.cluster)
50 s.ctx = auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
52 s.ctrUUID = arvadostest.QueuedContainerUUID
54 h := hmac.New(sha256.New, []byte(s.cluster.SystemRootToken))
55 fmt.Fprint(h, s.ctrUUID)
56 authKey := fmt.Sprintf("%x", h.Sum(nil))
58 s.gw = &crunchrun.Gateway{
59 DockerContainerID: new(string),
60 ContainerUUID: s.ctrUUID,
62 Address: "localhost:0",
63 Log: ctxlog.TestLogger(c),
64 ContainerIPAddress: func() (string, error) { return "localhost", nil },
66 c.Assert(s.gw.Start(), check.IsNil)
67 rootctx := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{s.cluster.SystemRootToken}})
68 _, err = s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
70 Attrs: map[string]interface{}{
71 "state": arvados.ContainerStateLocked}})
72 c.Assert(err, check.IsNil)
73 _, err = s.localdb.ContainerUpdate(rootctx, arvados.UpdateOptions{
75 Attrs: map[string]interface{}{
76 "state": arvados.ContainerStateRunning,
77 "gateway_address": s.gw.Address}})
78 c.Assert(err, check.IsNil)
81 func (s *ContainerGatewaySuite) SetUpTest(c *check.C) {
82 s.cluster.Containers.ShellAccess.Admin = true
83 s.cluster.Containers.ShellAccess.User = true
84 _, err := arvadostest.DB(c, s.cluster).Exec(`update containers set interactive_session_started=$1 where uuid=$2`, false, s.ctrUUID)
85 c.Check(err, check.IsNil)
88 func (s *ContainerGatewaySuite) TestConfig(c *check.C) {
89 for _, trial := range []struct {
95 {true, true, arvadostest.ActiveTokenV2, 0},
96 {true, false, arvadostest.ActiveTokenV2, 503},
97 {false, true, arvadostest.ActiveTokenV2, 0},
98 {false, false, arvadostest.ActiveTokenV2, 503},
99 {true, true, arvadostest.AdminToken, 0},
100 {true, false, arvadostest.AdminToken, 0},
101 {false, true, arvadostest.AdminToken, 403},
102 {false, false, arvadostest.AdminToken, 503},
104 c.Logf("trial %#v", trial)
105 s.cluster.Containers.ShellAccess.Admin = trial.configAdmin
106 s.cluster.Containers.ShellAccess.User = trial.configUser
107 ctx := auth.NewContext(s.ctx, &auth.Credentials{Tokens: []string{trial.sendToken}})
108 sshconn, err := s.localdb.ContainerSSH(ctx, arvados.ContainerSSHOptions{UUID: s.ctrUUID})
109 if trial.errorCode == 0 {
110 if !c.Check(err, check.IsNil) {
113 if !c.Check(sshconn.Conn, check.NotNil) {
118 c.Check(err, check.NotNil)
119 err, ok := err.(interface{ HTTPStatus() int })
120 if c.Check(ok, check.Equals, true) {
121 c.Check(err.HTTPStatus(), check.Equals, trial.errorCode)
127 func (s *ContainerGatewaySuite) TestDirectTCP(c *check.C) {
128 // Set up servers on a few TCP ports
130 for i := 0; i < 3; i++ {
131 ln, err := net.Listen("tcp", ":0")
132 c.Assert(err, check.IsNil)
134 addrs = append(addrs, ln.Addr().String())
137 conn, err := ln.Accept()
142 fmt.Fscanf(conn, "%s\n", &gotAddr)
143 c.Logf("stub server listening at %s received string %q from remote %s", ln.Addr().String(), gotAddr, conn.RemoteAddr())
144 if gotAddr == ln.Addr().String() {
145 fmt.Fprintf(conn, "%s\n", ln.Addr().String())
152 c.Logf("connecting to %s", s.gw.Address)
153 sshconn, err := s.localdb.ContainerSSH(s.ctx, arvados.ContainerSSHOptions{UUID: s.ctrUUID})
154 c.Assert(err, check.IsNil)
155 c.Assert(sshconn.Conn, check.NotNil)
156 defer sshconn.Conn.Close()
157 conn, chans, reqs, err := ssh.NewClientConn(sshconn.Conn, "zzzz-dz642-abcdeabcdeabcde", &ssh.ClientConfig{
158 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil },
160 c.Assert(err, check.IsNil)
161 client := ssh.NewClient(conn, chans, reqs)
162 for _, expectAddr := range addrs {
163 _, port, err := net.SplitHostPort(expectAddr)
164 c.Assert(err, check.IsNil)
166 c.Logf("trying foo:%s", port)
168 conn, err := client.Dial("tcp", "foo:"+port)
169 c.Assert(err, check.IsNil)
170 conn.SetDeadline(time.Now().Add(time.Second))
171 buf, err := ioutil.ReadAll(conn)
172 c.Check(err, check.IsNil)
173 c.Check(string(buf), check.Equals, "")
176 c.Logf("trying localhost:%s", port)
178 conn, err := client.Dial("tcp", "localhost:"+port)
179 c.Assert(err, check.IsNil)
180 conn.SetDeadline(time.Now().Add(time.Second))
181 conn.Write([]byte(expectAddr + "\n"))
183 fmt.Fscanf(conn, "%s\n", &gotAddr)
184 c.Check(gotAddr, check.Equals, expectAddr)
189 func (s *ContainerGatewaySuite) TestConnect(c *check.C) {
190 c.Logf("connecting to %s", s.gw.Address)
191 sshconn, err := s.localdb.ContainerSSH(s.ctx, arvados.ContainerSSHOptions{UUID: s.ctrUUID})
192 c.Assert(err, check.IsNil)
193 c.Assert(sshconn.Conn, check.NotNil)
194 defer sshconn.Conn.Close()
196 done := make(chan struct{})
200 // Receive text banner
201 buf := make([]byte, 12)
202 _, err := io.ReadFull(sshconn.Conn, buf)
203 c.Check(err, check.IsNil)
204 c.Check(string(buf), check.Equals, "SSH-2.0-Go\r\n")
207 _, err = sshconn.Conn.Write([]byte("SSH-2.0-Fake\r\n"))
208 c.Check(err, check.IsNil)
211 _, err = io.ReadFull(sshconn.Conn, buf[:4])
212 c.Check(err, check.IsNil)
213 c.Check(buf[:4], check.DeepEquals, []byte{0, 0, 1, 0xfc})
215 // If we can get this far into an SSH handshake...
216 c.Log("success, tunnel is working")
220 case <-time.After(time.Second):
223 ctr, err := s.localdb.ContainerGet(s.ctx, arvados.GetOptions{UUID: s.ctrUUID})
224 c.Check(err, check.IsNil)
225 c.Check(ctr.InteractiveSessionStarted, check.Equals, true)
228 func (s *ContainerGatewaySuite) TestConnectFail(c *check.C) {
229 c.Log("trying with no token")
230 ctx := auth.NewContext(context.Background(), &auth.Credentials{})
231 _, err := s.localdb.ContainerSSH(ctx, arvados.ContainerSSHOptions{UUID: s.ctrUUID})
232 c.Check(err, check.ErrorMatches, `.* 401 .*`)
234 c.Log("trying with anonymous token")
235 ctx = auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.AnonymousToken}})
236 _, err = s.localdb.ContainerSSH(ctx, arvados.ContainerSSHOptions{UUID: s.ctrUUID})
237 c.Check(err, check.ErrorMatches, `.* 404 .*`)