2023-04-10 15:36:59 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2023-02-07 22:42:25 +00:00
|
|
|
package workload_id
|
2023-02-02 18:59:14 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/e2e/e2eutil"
|
|
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
|
|
"github.com/shoenig/test/must"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TestWorkloadIdentity runs subtests exercising workload identity related
|
|
|
|
// functionality.
|
|
|
|
func TestWorkloadIdentity(t *testing.T) {
|
|
|
|
nomad := e2eutil.NomadClient(t)
|
|
|
|
|
|
|
|
e2eutil.WaitForLeader(t, nomad)
|
|
|
|
e2eutil.WaitForNodesReady(t, nomad, 1)
|
|
|
|
|
|
|
|
t.Run("testIdentity", testIdentity)
|
|
|
|
t.Run("testNobody", testNobody)
|
|
|
|
}
|
|
|
|
|
|
|
|
// testIdentity asserts that the various combinations of identity block
|
|
|
|
// parameteres produce the expected results.
|
|
|
|
func testIdentity(t *testing.T) {
|
|
|
|
nomad := e2eutil.NomadClient(t)
|
|
|
|
|
|
|
|
jobID := "identity-" + uuid.Short()
|
|
|
|
jobIDs := []string{jobID}
|
|
|
|
t.Cleanup(e2eutil.CleanupJobsAndGC(t, &jobIDs))
|
|
|
|
|
|
|
|
// start job
|
|
|
|
allocs := e2eutil.RegisterAndWaitForAllocs(t, nomad, "./input/identity.nomad", jobID, "")
|
|
|
|
must.Len(t, 1, allocs)
|
|
|
|
allocID := allocs[0].ID
|
|
|
|
|
|
|
|
// wait for batch alloc to complete
|
|
|
|
alloc := e2eutil.WaitForAllocStopped(t, nomad, allocID)
|
|
|
|
must.Eq(t, alloc.ClientStatus, "complete")
|
|
|
|
|
|
|
|
assertions := []struct {
|
|
|
|
task string
|
|
|
|
env bool
|
|
|
|
file bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
task: "none",
|
|
|
|
env: false,
|
|
|
|
file: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
task: "empty",
|
|
|
|
env: false,
|
|
|
|
file: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
task: "env",
|
|
|
|
env: true,
|
|
|
|
file: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
task: "file",
|
|
|
|
env: false,
|
|
|
|
file: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
task: "falsey",
|
|
|
|
env: false,
|
|
|
|
file: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the assertions and input file match
|
|
|
|
must.Len(t, len(assertions), alloc.Job.TaskGroups[0].Tasks,
|
|
|
|
must.Sprintf("test and jobspec mismatch"))
|
|
|
|
|
|
|
|
for _, tc := range assertions {
|
|
|
|
logFile := fmt.Sprintf("alloc/logs/%s.stdout.0", tc.task)
|
|
|
|
fd, err := nomad.AllocFS().Cat(alloc, logFile, nil)
|
|
|
|
must.NoError(t, err)
|
|
|
|
logBytes, err := io.ReadAll(fd)
|
|
|
|
must.NoError(t, err)
|
|
|
|
logs := string(logBytes)
|
|
|
|
|
|
|
|
ps := must.Sprintf("Task: %s Logs: <<EOF\n%sEOF", tc.task, logs)
|
|
|
|
|
|
|
|
must.StrHasSuffix(t, "done\n", logs, ps)
|
|
|
|
|
|
|
|
lines := strings.Split(logs, "\n")
|
|
|
|
switch {
|
|
|
|
case tc.env && tc.file:
|
|
|
|
must.Len(t, 4, lines, ps)
|
|
|
|
|
|
|
|
// Parse the env first
|
|
|
|
token := parseEnv(t, lines[1], ps)
|
|
|
|
|
|
|
|
// Assert the file length matches
|
|
|
|
n, err := strconv.Atoi(lines[0])
|
|
|
|
must.NoError(t, err, ps)
|
|
|
|
must.Eq(t, n, len(token), ps)
|
|
|
|
|
|
|
|
case !tc.env && tc.file:
|
|
|
|
must.Len(t, 3, lines, ps)
|
|
|
|
|
|
|
|
// Assert the length is > 10
|
|
|
|
n, err := strconv.Atoi(lines[0])
|
|
|
|
must.NoError(t, err, ps)
|
|
|
|
must.Greater(t, 10, n, ps)
|
|
|
|
|
|
|
|
case tc.env && !tc.file:
|
|
|
|
must.Len(t, 3, lines, ps)
|
|
|
|
|
|
|
|
parseEnv(t, lines[0], ps)
|
|
|
|
|
|
|
|
case !tc.env && !tc.file:
|
|
|
|
must.Len(t, 2, lines, ps)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseEnv(t *testing.T, line string, ps must.Setting) string {
|
|
|
|
must.StrHasPrefix(t, "NOMAD_TOKEN=", line, ps)
|
|
|
|
token := strings.Split(line, "=")[1]
|
|
|
|
must.Positive(t, len(token), ps)
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
|
|
|
|
// testNobody asserts that when task.user is set, the nomad_token file is owned
|
|
|
|
// by that user and has minimal permissions.
|
|
|
|
//
|
|
|
|
// Test assumes Client is running as root!
|
|
|
|
func testNobody(t *testing.T) {
|
|
|
|
nomad := e2eutil.NomadClient(t)
|
|
|
|
|
|
|
|
jobID := "nobodyid-" + uuid.Short()
|
|
|
|
jobIDs := []string{jobID}
|
|
|
|
t.Cleanup(e2eutil.CleanupJobsAndGC(t, &jobIDs))
|
|
|
|
|
|
|
|
// start job
|
|
|
|
allocs := e2eutil.RegisterAndWaitForAllocs(t, nomad, "./input/nobody.nomad", jobID, "")
|
|
|
|
must.Len(t, 1, allocs)
|
|
|
|
allocID := allocs[0].ID
|
|
|
|
|
|
|
|
// wait for batch alloc to complete
|
|
|
|
alloc := e2eutil.WaitForAllocStopped(t, nomad, allocID)
|
|
|
|
must.Eq(t, alloc.ClientStatus, "complete")
|
|
|
|
|
|
|
|
logFile := "alloc/logs/nobody.stdout.0"
|
|
|
|
fd, err := nomad.AllocFS().Cat(alloc, logFile, nil)
|
|
|
|
must.NoError(t, err)
|
|
|
|
logBytes, err := io.ReadAll(fd)
|
|
|
|
must.NoError(t, err)
|
|
|
|
logs := string(logBytes)
|
|
|
|
|
|
|
|
must.StrHasSuffix(t, "done\n", logs)
|
|
|
|
|
|
|
|
lines := strings.Split(logs, "\n")
|
|
|
|
must.Len(t, 3, lines)
|
|
|
|
parts := strings.Split(lines[0], " ")
|
|
|
|
stats := map[string]string{}
|
|
|
|
for _, p := range parts {
|
|
|
|
kvparts := strings.Split(p, "=")
|
|
|
|
stats[kvparts[0]] = kvparts[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
must.Eq(t, "0600", stats["perms"], must.Sprintf("is the client running as root?"))
|
|
|
|
must.Eq(t, "nobody", stats["username"], must.Sprintf("is the client running as root?"))
|
|
|
|
}
|