From 78c476d822deaa9e772f5ceceb7e40ea4b9c0de8 Mon Sep 17 00:00:00 2001 From: Tom Clegg Date: Tue, 1 Oct 2024 11:11:52 -0400 Subject: [PATCH] 21750: Add "has non-loopback ip" test. Checks that singularity containers can use networking even when not invoked as root. Arvados-DCO-1.1-Signed-off-by: Tom Clegg --- lib/crunchrun/executor_test.go | 35 ++++++++++++++++++++++++++++--- lib/crunchrun/singularity.go | 7 +++++++ lib/crunchrun/singularity_test.go | 23 ++++++++------------ 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lib/crunchrun/executor_test.go b/lib/crunchrun/executor_test.go index 7356f3b56a..134ca560ce 100644 --- a/lib/crunchrun/executor_test.go +++ b/lib/crunchrun/executor_test.go @@ -12,7 +12,9 @@ import ( "io/ioutil" "net" "net/http" + "net/netip" "os" + "regexp" "strings" "time" @@ -172,7 +174,7 @@ func (s *executorSuite) TestExecStdoutStderr(c *C) { c.Check(s.stderr.String(), Equals, "barwaz\n") } -func (s *executorSuite) TestIPAddress(c *C) { +func (s *executorSuite) TestEnableNetwork_Listen(c *C) { // Listen on an available port on the host. ln, err := net.Listen("tcp", net.JoinHostPort("0.0.0.0", "0")) c.Assert(err, IsNil) @@ -192,13 +194,15 @@ func (s *executorSuite) TestIPAddress(c *C) { defer cancel() for { + time.Sleep(time.Second / 10) if ctx.Err() != nil { c.Error("timed out") break } - time.Sleep(time.Second / 10) + ip, err := s.executor.IPAddress() if err != nil { + c.Logf("s.executor.IPAddress: %s", err) continue } c.Assert(ip, Not(Equals), "") @@ -208,7 +212,9 @@ func (s *executorSuite) TestIPAddress(c *C) { // process running inside the container, not the // net.Listen() running outside the container, even // though both listen on the same port. - req, err := http.NewRequest("BREW", "http://"+net.JoinHostPort(ip, port), nil) + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second)) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "BREW", "http://"+net.JoinHostPort(ip, port), nil) c.Assert(err, IsNil) resp, err := http.DefaultClient.Do(req) if err != nil { @@ -229,6 +235,29 @@ func (s *executorSuite) TestIPAddress(c *C) { c.Logf("stderr:\n%s\n\n", s.stderr.String()) } +func (s *executorSuite) TestEnableNetwork_IPAddress(c *C) { + s.spec.Command = []string{"ip", "ad"} + s.spec.EnableNetwork = true + c.Assert(s.executor.Create(s.spec), IsNil) + c.Assert(s.executor.Start(), IsNil) + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second)) + defer cancel() + code, _ := s.executor.Wait(ctx) + c.Check(code, Equals, 0) + c.Logf("stdout:\n%s\n\n", s.stdout.String()) + c.Logf("stderr:\n%s\n\n", s.stderr.String()) + + found := false + for _, m := range regexp.MustCompile(` inet (.+?)/`).FindAllStringSubmatch(s.stdout.String(), -1) { + if addr, err := netip.ParseAddr(m[1]); err == nil && !addr.IsLoopback() { + found = true + c.Logf("found non-loopback IP address %q", m[1]) + break + } + } + c.Check(found, Equals, true, Commentf("container does not appear to have a non-loopback IP address")) +} + func (s *executorSuite) TestInject(c *C) { hostdir := c.MkDir() c.Assert(os.WriteFile(hostdir+"/testfile", []byte("first tube"), 0777), IsNil) diff --git a/lib/crunchrun/singularity.go b/lib/crunchrun/singularity.go index 1492f9cc30..b94280a7f5 100644 --- a/lib/crunchrun/singularity.go +++ b/lib/crunchrun/singularity.go @@ -273,6 +273,13 @@ func (e *singularityExecutor) execCmd(path string) *exec.Cmd { // bookworm) because iptables now refuses to run in a // setuid environment. args = append(args, "--net", "--network=bridge") + } else { + // If we don't pass a --net argument at all, the + // container will be in the same network namespace as + // the host. + // + // Note this allows the container to listen on the + // host's external ports. } if e.spec.CUDADeviceCount != 0 { args = append(args, "--nv") diff --git a/lib/crunchrun/singularity_test.go b/lib/crunchrun/singularity_test.go index d91670814a..d1fa28ff08 100644 --- a/lib/crunchrun/singularity_test.go +++ b/lib/crunchrun/singularity_test.go @@ -9,7 +9,6 @@ import ( "os/exec" . "gopkg.in/check.v1" - check "gopkg.in/check.v1" ) var _ = Suite(&singularitySuite{}) @@ -23,11 +22,6 @@ func (s *singularitySuite) SetUpSuite(c *C) { if err != nil { c.Skip("looks like singularity is not installed") } - uuc, err := os.ReadFile("/proc/sys/kernel/unprivileged_userns_clone") - c.Assert(err, check.IsNil) - if string(uuc) == "0\n" { - c.Skip("insufficient privileges to run singularity tests -- `singularity exec --fakeroot` requires /proc/sys/kernel/unprivileged_userns_clone = 1") - } s.newExecutor = func(c *C) { var err error s.executor, err = newSingularityExecutor(c.Logf) @@ -41,20 +35,21 @@ func (s *singularitySuite) TearDownSuite(c *C) { } } -func (s *singularitySuite) TestIPAddress(c *C) { +func (s *singularitySuite) TestEnableNetwork_Listen(c *C) { // With modern iptables, singularity (as of 4.2.1) cannot // enable networking when invoked by a regular user. Under // arvados-dispatch-cloud, crunch-run runs as root, so it's // OK. For testing, assuming tests are not running as root, we // use sudo -- but only if requested via environment variable. - if os.Getuid() != 0 { - if os.Getenv("ARVADOS_TEST_USE_SUDO") != "" { - s.executor.(*singularityExecutor).sudo = true - } else { - c.Skip("test case needs to run singularity as root -- set ARVADOS_TEST_USE_SUDO=1 to enable this test") - } + if os.Getuid() == 0 { + // already root + } else if os.Getenv("ARVADOS_TEST_USE_SUDO") != "" { + c.Logf("ARVADOS_TEST_USE_SUDO is set, invoking 'sudo singularity ...'") + s.executor.(*singularityExecutor).sudo = true + } else { + c.Skip("test case needs to run singularity as root -- set ARVADOS_TEST_USE_SUDO=1 to enable this test") } - s.executorSuite.TestIPAddress(c) + s.executorSuite.TestEnableNetwork_Listen(c) } func (s *singularitySuite) TestInject(c *C) { -- 2.39.5