open-nomad/helper/users/lookup_linux_test.go
Michael Schurter 0a496c845e
Task API via Unix Domain Socket (#15864)
This change introduces the Task API: a portable way for tasks to access Nomad's HTTP API. This particular implementation uses a Unix Domain Socket and, unlike the agent's HTTP API, always requires authentication even if ACLs are disabled.

This PR contains the core feature and tests but followup work is required for the following TODO items:

- Docs - might do in a followup since dynamic node metadata / task api / workload id all need to interlink
- Unit tests for auth middleware
- Caching for auth middleware
- Rate limiting on negative lookups for auth middleware

---------

Co-authored-by: Seth Hoenig <shoenig@duck.com>
2023-02-06 11:31:22 -08:00

118 lines
3.4 KiB
Go

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