Merge branch '20718-grpc-upgrade'. Closes #20718
[arvados.git] / lib / pam / docker_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package main
6
7 import (
8         "bytes"
9         "crypto/tls"
10         "fmt"
11         "io/ioutil"
12         "net"
13         "net/http"
14         "net/http/httputil"
15         "net/url"
16         "os"
17         "os/exec"
18         "strings"
19         "testing"
20
21         "git.arvados.org/arvados.git/sdk/go/arvadostest"
22         "gopkg.in/check.v1"
23 )
24
25 type DockerSuite struct {
26         tmpdir   string
27         hostip   string
28         proxyln  net.Listener
29         proxysrv *http.Server
30 }
31
32 var _ = check.Suite(&DockerSuite{})
33
34 func Test(t *testing.T) { check.TestingT(t) }
35
36 func (s *DockerSuite) SetUpSuite(c *check.C) {
37         if testing.Short() {
38                 c.Skip("skipping docker tests in short mode")
39         } else if _, err := exec.Command("docker", "info").CombinedOutput(); err != nil {
40                 c.Skip("skipping docker tests because docker is not available")
41         }
42
43         s.tmpdir = c.MkDir()
44
45         // The integration-testing controller listens on the loopback
46         // interface, so it won't be reachable directly from the
47         // docker container -- so here we run a proxy on 0.0.0.0 for
48         // the duration of the test.
49         hostips, err := exec.Command("hostname", "-I").Output()
50         c.Assert(err, check.IsNil)
51         s.hostip = strings.Split(strings.Trim(string(hostips), "\n"), " ")[0]
52         ln, err := net.Listen("tcp", s.hostip+":0")
53         c.Assert(err, check.IsNil)
54         s.proxyln = ln
55         proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "https", Host: os.Getenv("ARVADOS_API_HOST")})
56         proxy.Transport = &http.Transport{
57                 TLSClientConfig: &tls.Config{
58                         InsecureSkipVerify: true,
59                 },
60         }
61         s.proxysrv = &http.Server{Handler: proxy}
62         go s.proxysrv.ServeTLS(ln, "../../services/api/tmp/self-signed.pem", "../../services/api/tmp/self-signed.key")
63
64         // Build a pam module to install & configure in the docker
65         // container.
66         cmd := exec.Command("go", "build", "-buildmode=c-shared", "-o", s.tmpdir+"/pam_arvados.so")
67         cmd.Stdout = os.Stdout
68         cmd.Stderr = os.Stderr
69         err = cmd.Run()
70         c.Assert(err, check.IsNil)
71
72         // Build the testclient program that will (from inside the
73         // docker container) configure the system to use the above PAM
74         // config, and then try authentication.
75         cmd = exec.Command("go", "build", "-o", s.tmpdir+"/testclient", "./testclient.go")
76         cmd.Stdout = os.Stdout
77         cmd.Stderr = os.Stderr
78         err = cmd.Run()
79         c.Assert(err, check.IsNil)
80 }
81
82 func (s *DockerSuite) TearDownSuite(c *check.C) {
83         if s.proxysrv != nil {
84                 s.proxysrv.Close()
85         }
86         if s.proxyln != nil {
87                 s.proxyln.Close()
88         }
89 }
90
91 func (s *DockerSuite) SetUpTest(c *check.C) {
92         // Write a PAM config file that uses our proxy as
93         // ARVADOS_API_HOST.
94         proxyhost := s.proxyln.Addr().String()
95         confdata := fmt.Sprintf(`Name: Arvados authentication
96 Default: yes
97 Priority: 256
98 Auth-Type: Primary
99 Auth:
100         [success=end default=ignore]    /usr/lib/pam_arvados.so %s testvm2.shell insecure
101 Auth-Initial:
102         [success=end default=ignore]    /usr/lib/pam_arvados.so %s testvm2.shell insecure
103 `, proxyhost, proxyhost)
104         err := ioutil.WriteFile(s.tmpdir+"/conffile", []byte(confdata), 0755)
105         c.Assert(err, check.IsNil)
106 }
107
108 func (s *DockerSuite) runTestClient(c *check.C, args ...string) (stdout, stderr *bytes.Buffer, err error) {
109
110         cmd := exec.Command("docker", append([]string{
111                 "run", "--rm",
112                 "--hostname", "testvm2.shell",
113                 "--add-host", "zzzzz.arvadosapi.com:" + s.hostip,
114                 "-v", s.tmpdir + "/pam_arvados.so:/usr/lib/pam_arvados.so:ro",
115                 "-v", s.tmpdir + "/conffile:/usr/share/pam-configs/arvados:ro",
116                 "-v", s.tmpdir + "/testclient:/testclient:ro",
117                 "debian:buster",
118                 "/testclient"}, args...)...)
119         stdout = &bytes.Buffer{}
120         stderr = &bytes.Buffer{}
121         cmd.Stdout = stdout
122         cmd.Stderr = stderr
123         err = cmd.Run()
124         return
125 }
126
127 func (s *DockerSuite) TestSuccess(c *check.C) {
128         stdout, stderr, err := s.runTestClient(c, "try", "active", arvadostest.ActiveTokenV2)
129         c.Check(err, check.IsNil)
130         c.Logf("%s", stderr.String())
131         c.Check(stdout.String(), check.Equals, "")
132         c.Check(stderr.String(), check.Matches, `(?ms).*authentication succeeded.*`)
133 }
134
135 func (s *DockerSuite) TestFailure(c *check.C) {
136         for _, trial := range []struct {
137                 label    string
138                 username string
139                 token    string
140         }{
141                 {"bad token", "active", arvadostest.ActiveTokenV2 + "badtoken"},
142                 {"empty token", "active", ""},
143                 {"empty username", "", arvadostest.ActiveTokenV2},
144                 {"wrong username", "wrongusername", arvadostest.ActiveTokenV2},
145         } {
146                 c.Logf("trial: %s", trial.label)
147                 stdout, stderr, err := s.runTestClient(c, "try", trial.username, trial.token)
148                 c.Logf("%s", stderr.String())
149                 c.Check(err, check.NotNil)
150                 c.Check(stdout.String(), check.Equals, "")
151                 c.Check(stderr.String(), check.Matches, `(?ms).*authentication failed.*`)
152         }
153 }
154
155 func (s *DockerSuite) TestDefaultHostname(c *check.C) {
156         confdata := fmt.Sprintf(`Name: Arvados authentication
157 Default: yes
158 Priority: 256
159 Auth-Type: Primary
160 Auth:
161         [success=end default=ignore]    /usr/lib/pam_arvados.so %s - insecure debug
162 Auth-Initial:
163         [success=end default=ignore]    /usr/lib/pam_arvados.so %s - insecure debug
164 `, s.proxyln.Addr().String(), s.proxyln.Addr().String())
165         err := ioutil.WriteFile(s.tmpdir+"/conffile", []byte(confdata), 0755)
166         c.Assert(err, check.IsNil)
167
168         stdout, stderr, err := s.runTestClient(c, "try", "active", arvadostest.ActiveTokenV2)
169         c.Check(err, check.IsNil)
170         c.Logf("%s", stderr.String())
171         c.Check(stdout.String(), check.Equals, "")
172         c.Check(stderr.String(), check.Matches, `(?ms).*authentication succeeded.*`)
173 }