2023-02-02 18:59:14 +00:00
|
|
|
//go:build linux
|
|
|
|
|
|
|
|
package users
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/user"
|
|
|
|
"path/filepath"
|
|
|
|
"syscall"
|
|
|
|
"testing"
|
|
|
|
|
2023-02-06 19:31:22 +00:00
|
|
|
"github.com/hashicorp/nomad/helper/testlog"
|
2023-02-02 18:59:14 +00:00
|
|
|
"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 TestLookup_NobodyIDs(t *testing.T) {
|
|
|
|
uid, gid := NobodyIDs()
|
|
|
|
must.Eq(t, 65534, uid) // ubuntu
|
|
|
|
must.Eq(t, 65534, gid) // ubuntu
|
|
|
|
}
|
|
|
|
|
|
|
|
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(),
|
2023-02-06 19:31:22 +00:00
|
|
|
must.Sprintf("expected %s to be a regular file but found %#o", path, stat.Mode()))
|
2023-02-02 18:59:14 +00:00
|
|
|
|
|
|
|
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()))
|
|
|
|
}
|
|
|
|
}
|
2023-02-06 19:31:22 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
defer 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()))
|
|
|
|
}
|
|
|
|
}
|