0a496c845e
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>
118 lines
3.4 KiB
Go
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()))
|
|
}
|
|
}
|