// 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())) } }