github.com/hernad/nomad@v1.6.112/e2e/workload_id/workload_id_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package workload_id
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"strconv"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/hernad/nomad/e2e/e2eutil"
    14  	"github.com/hernad/nomad/helper/uuid"
    15  	"github.com/shoenig/test/must"
    16  )
    17  
    18  // TestWorkloadIdentity runs subtests exercising workload identity related
    19  // functionality.
    20  func TestWorkloadIdentity(t *testing.T) {
    21  	nomad := e2eutil.NomadClient(t)
    22  
    23  	e2eutil.WaitForLeader(t, nomad)
    24  	e2eutil.WaitForNodesReady(t, nomad, 1)
    25  
    26  	t.Run("testIdentity", testIdentity)
    27  	t.Run("testNobody", testNobody)
    28  }
    29  
    30  // testIdentity asserts that the various combinations of identity block
    31  // parameteres produce the expected results.
    32  func testIdentity(t *testing.T) {
    33  	nomad := e2eutil.NomadClient(t)
    34  
    35  	jobID := "identity-" + uuid.Short()
    36  	jobIDs := []string{jobID}
    37  	t.Cleanup(e2eutil.CleanupJobsAndGC(t, &jobIDs))
    38  
    39  	// start job
    40  	allocs := e2eutil.RegisterAndWaitForAllocs(t, nomad, "./input/identity.nomad", jobID, "")
    41  	must.Len(t, 1, allocs)
    42  	allocID := allocs[0].ID
    43  
    44  	// wait for batch alloc to complete
    45  	alloc := e2eutil.WaitForAllocStopped(t, nomad, allocID)
    46  	must.Eq(t, alloc.ClientStatus, "complete")
    47  
    48  	assertions := []struct {
    49  		task string
    50  		env  bool
    51  		file bool
    52  	}{
    53  		{
    54  			task: "none",
    55  			env:  false,
    56  			file: false,
    57  		},
    58  		{
    59  			task: "empty",
    60  			env:  false,
    61  			file: false,
    62  		},
    63  		{
    64  			task: "env",
    65  			env:  true,
    66  			file: false,
    67  		},
    68  		{
    69  			task: "file",
    70  			env:  false,
    71  			file: true,
    72  		},
    73  		{
    74  			task: "falsey",
    75  			env:  false,
    76  			file: false,
    77  		},
    78  	}
    79  
    80  	// Ensure the assertions and input file match
    81  	must.Len(t, len(assertions), alloc.Job.TaskGroups[0].Tasks,
    82  		must.Sprintf("test and jobspec mismatch"))
    83  
    84  	for _, tc := range assertions {
    85  		logFile := fmt.Sprintf("alloc/logs/%s.stdout.0", tc.task)
    86  		fd, err := nomad.AllocFS().Cat(alloc, logFile, nil)
    87  		must.NoError(t, err)
    88  		logBytes, err := io.ReadAll(fd)
    89  		must.NoError(t, err)
    90  		logs := string(logBytes)
    91  
    92  		ps := must.Sprintf("Task: %s Logs: <<EOF\n%sEOF", tc.task, logs)
    93  
    94  		must.StrHasSuffix(t, "done\n", logs, ps)
    95  
    96  		lines := strings.Split(logs, "\n")
    97  		switch {
    98  		case tc.env && tc.file:
    99  			must.Len(t, 4, lines, ps)
   100  
   101  			// Parse the env first
   102  			token := parseEnv(t, lines[1], ps)
   103  
   104  			// Assert the file length matches
   105  			n, err := strconv.Atoi(lines[0])
   106  			must.NoError(t, err, ps)
   107  			must.Eq(t, n, len(token), ps)
   108  
   109  		case !tc.env && tc.file:
   110  			must.Len(t, 3, lines, ps)
   111  
   112  			// Assert the length is > 10
   113  			n, err := strconv.Atoi(lines[0])
   114  			must.NoError(t, err, ps)
   115  			must.Greater(t, 10, n, ps)
   116  
   117  		case tc.env && !tc.file:
   118  			must.Len(t, 3, lines, ps)
   119  
   120  			parseEnv(t, lines[0], ps)
   121  
   122  		case !tc.env && !tc.file:
   123  			must.Len(t, 2, lines, ps)
   124  		}
   125  	}
   126  
   127  }
   128  
   129  func parseEnv(t *testing.T, line string, ps must.Setting) string {
   130  	must.StrHasPrefix(t, "NOMAD_TOKEN=", line, ps)
   131  	token := strings.Split(line, "=")[1]
   132  	must.Positive(t, len(token), ps)
   133  	return token
   134  }
   135  
   136  // testNobody asserts that when task.user is set, the nomad_token file is owned
   137  // by that user and has minimal permissions.
   138  //
   139  // Test assumes Client is running as root!
   140  func testNobody(t *testing.T) {
   141  	nomad := e2eutil.NomadClient(t)
   142  
   143  	jobID := "nobodyid-" + uuid.Short()
   144  	jobIDs := []string{jobID}
   145  	t.Cleanup(e2eutil.CleanupJobsAndGC(t, &jobIDs))
   146  
   147  	// start job
   148  	allocs := e2eutil.RegisterAndWaitForAllocs(t, nomad, "./input/nobody.nomad", jobID, "")
   149  	must.Len(t, 1, allocs)
   150  	allocID := allocs[0].ID
   151  
   152  	// wait for batch alloc to complete
   153  	alloc := e2eutil.WaitForAllocStopped(t, nomad, allocID)
   154  	must.Eq(t, alloc.ClientStatus, "complete")
   155  
   156  	logFile := "alloc/logs/nobody.stdout.0"
   157  	fd, err := nomad.AllocFS().Cat(alloc, logFile, nil)
   158  	must.NoError(t, err)
   159  	logBytes, err := io.ReadAll(fd)
   160  	must.NoError(t, err)
   161  	logs := string(logBytes)
   162  
   163  	must.StrHasSuffix(t, "done\n", logs)
   164  
   165  	lines := strings.Split(logs, "\n")
   166  	must.Len(t, 3, lines)
   167  	parts := strings.Split(lines[0], " ")
   168  	stats := map[string]string{}
   169  	for _, p := range parts {
   170  		kvparts := strings.Split(p, "=")
   171  		stats[kvparts[0]] = kvparts[1]
   172  	}
   173  
   174  	must.Eq(t, "0600", stats["perms"], must.Sprintf("is the client running as root?"))
   175  	must.Eq(t, "nobody", stats["username"], must.Sprintf("is the client running as root?"))
   176  }