Files
nomad/helper/users/lookup_linux_test.go
Seth Hoenig ed0dfd2ffb users: eliminate nobody user memoization (#16904)
This PR eliminates code specific to looking up and caching the uid/gid/user.User
object associated with the nobody user in an init block. This code existed before
adding the generic users cache and was meant to optimize the one search path we
knew would happen often. Now that we have the cache, seems reasonable to eliminate
this init block and use the cache instead like for any other user.

Also fixes a constraint on the podman (and other) drivers, where building without
CGO became problematic on some OS like Fedora IoT where the nobody user cannot
be found with the pure-Go standard library.

Fixes github.com/hashicorp/nomad-driver-podman/issues/228
2023-04-17 12:30:30 -05:00

117 lines
3.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build linux
package users
import (
"errors"
"fmt"
"os"
"os/user"
"path/filepath"
"syscall"
"testing"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/shoenig/test/must"
"golang.org/x/sys/unix"
)
func TestLookup_Linux(t *testing.T) {
cases := []struct {
username string
expErr error
expUser *user.User
}{
{username: "nobody", expUser: &user.User{Username: "nobody", Uid: "65534", Gid: "65534", Name: "nobody", HomeDir: "/nonexistent"}}, // ubuntu
{username: "root", expUser: &user.User{Username: "root", Uid: "0", Gid: "0", Name: "root", HomeDir: "/root"}},
{username: "doesnotexist", expErr: errors.New("user: unknown user doesnotexist")},
}
for _, tc := range cases {
t.Run(tc.username, func(t *testing.T) {
u, err := Lookup(tc.username)
if tc.expErr != nil {
must.EqError(t, tc.expErr, err.Error())
} else {
must.Eq(t, tc.expUser, u)
}
})
}
}
func TestWriteFileFor_Linux(t *testing.T) {
// This is really how you have to retrieve umask. See `man 2 umask`
umask := unix.Umask(0)
unix.Umask(umask)
path := filepath.Join(t.TempDir(), "secret.txt")
contents := []byte("TOO MANY SECRETS")
must.NoError(t, WriteFileFor(path, contents, "nobody"))
stat, err := os.Lstat(path)
must.NoError(t, err)
must.True(t, stat.Mode().IsRegular(),
must.Sprintf("expected %s to be a regular file but found %#o", path, stat.Mode()))
linuxStat, ok := stat.Sys().(*syscall.Stat_t)
must.True(t, ok, must.Sprintf("expected stat.Sys() to be a *syscall.Stat_t but found %T", stat.Sys()))
current, err := Current()
must.NoError(t, err)
if current.Username == "root" {
t.Logf("Running as root: asserting %s is owned by nobody", path)
nobody, err := Lookup("nobody")
must.NoError(t, err)
must.Eq(t, nobody.Uid, fmt.Sprintf("%d", linuxStat.Uid))
must.Eq(t, 0o600&(^umask), int(stat.Mode()))
} else {
t.Logf("Running as non-root: asserting %s is world readable", path)
must.Eq(t, current.Uid, fmt.Sprintf("%d", linuxStat.Uid))
must.Eq(t, 0o666&(^umask), int(stat.Mode()))
}
}
// TestSocketFileFor_Linux asserts that when running as root on Linux socket
// files are created with least permissions. If running as non-root then we
// leave the socket file as world writable.
func TestSocketFileFor_Linux(t *testing.T) {
path := filepath.Join(t.TempDir(), "api.sock")
logger := testlog.HCLogger(t)
ln, err := SocketFileFor(logger, path, "nobody")
must.NoError(t, err)
must.NotNil(t, ln)
t.Cleanup(func() {
_ = ln.Close()
})
stat, err := os.Lstat(path)
must.NoError(t, err)
must.False(t, stat.Mode().IsRegular(),
must.Sprintf("expected %s to be a regular file but found %#o", path, stat.Mode()))
linuxStat, ok := stat.Sys().(*syscall.Stat_t)
must.True(t, ok, must.Sprintf("expected stat.Sys() to be a *syscall.Stat_t but found %T", stat.Sys()))
current, err := Current()
must.NoError(t, err)
if current.Username == "root" {
t.Logf("Running as root: asserting %s is owned by nobody", path)
nobody, err := Lookup("nobody")
must.NoError(t, err)
must.Eq(t, nobody.Uid, fmt.Sprintf("%d", linuxStat.Uid))
must.Eq(t, 0o600, int(stat.Mode().Perm()))
} else {
t.Logf("Running as non-root: asserting %s is world writable", path)
must.Eq(t, current.Uid, fmt.Sprintf("%d", linuxStat.Uid))
must.Eq(t, 0o666, int(stat.Mode().Perm()))
}
}