15348: Add Go-based PAM module.
[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         proxyhost := ln.Addr().String()
64
65         // Build a pam module to install & configure in the docker
66         // container.
67         cmd := exec.Command("go", "build", "-buildmode=c-shared", "-o", s.tmpdir+"/pam_arvados.so")
68         cmd.Stdout = os.Stdout
69         cmd.Stderr = os.Stderr
70         err = cmd.Run()
71         c.Assert(err, check.IsNil)
72
73         // Write a PAM config file that uses our proxy as
74         // ARVADOS_API_HOST.
75         confdata := fmt.Sprintf(`Name: Arvados authentication
76 Default: yes
77 Priority: 256
78 Auth-Type: Primary
79 Auth:
80         [success=end default=ignore]    /usr/lib/security/pam_arvados.so %s testvm2.shell insecure
81 Auth-Initial:
82         [success=end default=ignore]    /usr/lib/security/pam_arvados.so %s testvm2.shell insecure
83 `, proxyhost, proxyhost)
84         err = ioutil.WriteFile(s.tmpdir+"/conffile", []byte(confdata), 0755)
85         c.Assert(err, check.IsNil)
86
87         // Build the testclient program that will (from inside the
88         // docker container) configure the system to use the above PAM
89         // config, and then try authentication.
90         cmd = exec.Command("go", "build", "-o", s.tmpdir+"/testclient", "./testclient.go")
91         cmd.Stdout = os.Stdout
92         cmd.Stderr = os.Stderr
93         err = cmd.Run()
94         c.Assert(err, check.IsNil)
95 }
96
97 func (s *DockerSuite) TearDownSuite(c *check.C) {
98         s.proxysrv.Close()
99         s.proxyln.Close()
100 }
101
102 func (s *DockerSuite) runTestClient(c *check.C, args ...string) (stdout, stderr *bytes.Buffer, err error) {
103         cmd := exec.Command("docker", append([]string{
104                 "run", "--rm",
105                 "--add-host", "zzzzz.arvadosapi.com:" + s.hostip,
106                 "-v", s.tmpdir + "/pam_arvados.so:/usr/lib/security/pam_arvados.so:ro",
107                 "-v", s.tmpdir + "/conffile:/usr/share/pam-configs/arvados:ro",
108                 "-v", s.tmpdir + "/testclient:/testclient:ro",
109                 "debian:buster",
110                 "/testclient"}, args...)...)
111         stdout = &bytes.Buffer{}
112         stderr = &bytes.Buffer{}
113         cmd.Stdout = stdout
114         cmd.Stderr = stderr
115         err = cmd.Run()
116         return
117 }
118
119 func (s *DockerSuite) TestSuccess(c *check.C) {
120         stdout, stderr, err := s.runTestClient(c, "try", "active", arvadostest.ActiveTokenV2)
121         c.Check(err, check.IsNil)
122         c.Check(stdout.String(), check.Equals, "")
123         c.Check(stderr.String(), check.Matches, `(?ms).*authentication succeeded.*`)
124 }
125
126 func (s *DockerSuite) TestFailure(c *check.C) {
127         for _, trial := range []struct {
128                 label    string
129                 username string
130                 token    string
131         }{
132                 {"bad token", "active", arvadostest.ActiveTokenV2 + "badtoken"},
133                 {"empty token", "active", ""},
134                 {"empty username", "", arvadostest.ActiveTokenV2},
135                 {"wrong username", "wrongusername", arvadostest.ActiveTokenV2},
136         } {
137                 c.Logf("trial: %s", trial.label)
138                 stdout, stderr, err := s.runTestClient(c, "try", trial.username, trial.token)
139                 c.Check(err, check.NotNil)
140                 c.Check(stdout.String(), check.Equals, "")
141                 c.Check(stderr.String(), check.Matches, `(?ms).*authentication failed.*`)
142         }
143 }