diff --git a/drivers/exec/driver.go b/drivers/exec/driver.go index 072b430a7..7ec930810 100644 --- a/drivers/exec/driver.go +++ b/drivers/exec/driver.go @@ -20,8 +20,10 @@ import ( "github.com/hashicorp/nomad/drivers/shared/eventer" "github.com/hashicorp/nomad/drivers/shared/executor" "github.com/hashicorp/nomad/drivers/shared/resolvconf" + "github.com/hashicorp/nomad/drivers/shared/validators" "github.com/hashicorp/nomad/helper/pluginutils/loader" "github.com/hashicorp/nomad/helper/pointer" + "github.com/hashicorp/nomad/helper/users" "github.com/hashicorp/nomad/plugins/base" "github.com/hashicorp/nomad/plugins/drivers" "github.com/hashicorp/nomad/plugins/drivers/fsisolation" @@ -83,6 +85,8 @@ var ( hclspec.NewAttr("allow_caps", "list(string)", false), hclspec.NewLiteral(capabilities.HCLSpecLiteral), ), + "denied_host_uids": hclspec.NewAttr("denied_host_uids", "string", false), + "denied_host_gids": hclspec.NewAttr("denied_host_gids", "string", false), }) // taskConfigSpec is the hcl specification for the driver config section of @@ -159,6 +163,12 @@ type Config struct { // AllowCaps configures which Linux Capabilities are enabled for tasks // running on this node. AllowCaps []string `codec:"allow_caps"` + + // DeniedHostUids configures which host uids are disallowed + DeniedHostUids string `codec:"denied_host_uids"` + + // DeniedHostGids configures which host gids are disallowed + DeniedHostGids string `codec:"denied_host_gids"` } func (c *Config) validate() error { @@ -179,6 +189,14 @@ func (c *Config) validate() error { return fmt.Errorf("allow_caps configured with capabilities not supported by system: %s", badCaps) } + if err := validators.IDRangeValid("denied_host_uids", c.DeniedHostUids); err != nil { + return err + } + + if err := validators.IDRangeValid("denied_host_gids", c.DeniedHostGids); err != nil { + return err + } + return nil } @@ -205,7 +223,7 @@ type TaskConfig struct { CapDrop []string `codec:"cap_drop"` } -func (tc *TaskConfig) validate() error { +func (tc *TaskConfig) validate(cfg *drivers.TaskConfig, driverConfig *Config) error { switch tc.ModePID { case "", executor.IsolationModePrivate, executor.IsolationModeHost: default: @@ -228,6 +246,12 @@ func (tc *TaskConfig) validate() error { return fmt.Errorf("cap_drop configured with capabilities not supported by system: %s", badDrops) } + usernameToLookup := getUsername(cfg) + + if err := validators.UserInRange(users.Lookup, usernameToLookup, driverConfig.DeniedHostUids, driverConfig.DeniedHostGids); err != nil { + return err + } + return nil } @@ -433,7 +457,7 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive return nil, nil, fmt.Errorf("failed to decode driver config: %v", err) } - if err := driverConfig.validate(); err != nil { + if err := driverConfig.validate(cfg, &d.config); err != nil { return nil, nil, fmt.Errorf("failed driver config validation: %v", err) } @@ -456,10 +480,7 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive return nil, nil, fmt.Errorf("failed to create executor: %v", err) } - user := cfg.User - if user == "" { - user = "nobody" - } + user := getUsername(cfg) if cfg.DNS != nil { dnsMount, err := resolvconf.GenerateDNSMount(cfg.TaskDir().Dir, cfg.DNS) @@ -689,3 +710,12 @@ func (d *Driver) ExecTaskStreamingRaw(ctx context.Context, return handle.exec.ExecStreaming(ctx, command, tty, stream) } + +func getUsername(cfg *drivers.TaskConfig) string { + username := "nobody" + if cfg.User != "" { + username = cfg.User + } + + return username +} diff --git a/drivers/exec/driver_test.go b/drivers/exec/driver_test.go index 604295305..70617068d 100644 --- a/drivers/exec/driver_test.go +++ b/drivers/exec/driver_test.go @@ -26,6 +26,7 @@ import ( "github.com/hashicorp/nomad/helper/pluginutils/hclutils" "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/helper/testtask" + "github.com/hashicorp/nomad/helper/users" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/plugins/base" @@ -870,10 +871,59 @@ func TestDriver_Config_validate(t *testing.T) { }).validate()) } }) + + t.Run("denied_host_ids", func(t *testing.T) { + invalidUidRange := "invalid uid range" + invalidGidRange := "invalid gid range" + + for _, tc := range []struct { + uidRanges string + gidRanges string + errorStr *string + }{ + {uidRanges: "", gidRanges: "", errorStr: nil}, + {uidRanges: "1-10", gidRanges: "1-10", errorStr: nil}, + {uidRanges: "10-1", gidRanges: "", errorStr: &invalidUidRange}, + {uidRanges: "", gidRanges: "10-1", errorStr: &invalidGidRange}, + } { + + validationErr := (&Config{ + DefaultModePID: "private", + DefaultModeIPC: "private", + DeniedHostUids: tc.uidRanges, + DeniedHostGids: tc.gidRanges, + }).validate() + + if tc.errorStr == nil { + require.Nil(t, validationErr) + } else { + require.Contains(t, validationErr.Error(), *tc.errorStr) + } + } + }) } func TestDriver_TaskConfig_validate(t *testing.T) { ci.Parallel(t) + + current, err := users.Current() + require.NoError(t, err) + currentUid, err := strconv.ParseUint(current.Uid, 10, 32) + require.NoError(t, err) + nobody, err := users.Lookup("nobody") + require.NoError(t, err) + nobodyUid, err := strconv.ParseUint(nobody.Uid, 10, 32) + require.NoError(t, err) + + allowAll := "" + denyCurrent := fmt.Sprint(currentUid) + denyNobody := fmt.Sprint(nobodyUid) + configAllowCurrent := Config{DeniedHostUids: allowAll} + configDenyCurrent := Config{DeniedHostUids: denyCurrent} + configDenyAnonymous := Config{DeniedHostUids: denyNobody} + driverConfigNoUserSpecified := drivers.TaskConfig{} + driverConfigSpecifyCurrent := drivers.TaskConfig{User: current.Name} + t.Run("pid/ipc", func(t *testing.T) { for _, tc := range []struct { pidMode, ipcMode string @@ -892,7 +942,7 @@ func TestDriver_TaskConfig_validate(t *testing.T) { require.Equal(t, tc.exp, (&TaskConfig{ ModePID: tc.pidMode, ModeIPC: tc.ipcMode, - }).validate()) + }).validate(&driverConfigNoUserSpecified, &configAllowCurrent)) } }) @@ -909,7 +959,7 @@ func TestDriver_TaskConfig_validate(t *testing.T) { } { require.Equal(t, tc.exp, (&TaskConfig{ CapAdd: tc.adds, - }).validate()) + }).validate(&driverConfigNoUserSpecified, &configAllowCurrent)) } }) @@ -926,7 +976,25 @@ func TestDriver_TaskConfig_validate(t *testing.T) { } { require.Equal(t, tc.exp, (&TaskConfig{ CapDrop: tc.drops, - }).validate()) + }).validate(&driverConfigNoUserSpecified, &configAllowCurrent)) + } + }) + + t.Run("uid_restriction", func(t *testing.T) { + currentUserErrStr := fmt.Sprintf("running as uid %d is disallowed", currentUid) + anonUserErrStr := fmt.Sprintf("running as uid %d is disallowed", nobodyUid) + + for _, tc := range []struct { + config Config + driverConfig drivers.TaskConfig + exp error + }{ + {config: configAllowCurrent, driverConfig: driverConfigSpecifyCurrent, exp: nil}, + {config: configDenyCurrent, driverConfig: driverConfigNoUserSpecified, exp: nil}, + {config: configDenyCurrent, driverConfig: driverConfigSpecifyCurrent, exp: errors.New(currentUserErrStr)}, + {config: configDenyAnonymous, driverConfig: driverConfigNoUserSpecified, exp: errors.New(anonUserErrStr)}, + } { + require.Equal(t, tc.exp, (&TaskConfig{}).validate(&tc.driverConfig, &tc.config)) } }) } diff --git a/drivers/rawexec/driver.go b/drivers/rawexec/driver.go index 9e59488a9..e6c7b8c24 100644 --- a/drivers/rawexec/driver.go +++ b/drivers/rawexec/driver.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/nomad/drivers/shared/eventer" "github.com/hashicorp/nomad/drivers/shared/executor" "github.com/hashicorp/nomad/helper/pluginutils/hclutils" + "github.com/hashicorp/nomad/drivers/shared/validators" "github.com/hashicorp/nomad/helper/pluginutils/loader" "github.com/hashicorp/nomad/plugins/base" "github.com/hashicorp/nomad/plugins/drivers" @@ -81,6 +82,8 @@ var ( hclspec.NewAttr("enabled", "bool", false), hclspec.NewLiteral("false"), ), + "denied_host_uids": hclspec.NewAttr("denied_host_uids", "string", false), + "denied_host_gids": hclspec.NewAttr("denied_host_gids", "string", false), }) // taskConfigSpec is the hcl specification for the driver config section of @@ -139,6 +142,12 @@ type Driver struct { type Config struct { // Enabled is set to true to enable the raw_exec driver Enabled bool `codec:"enabled"` + + // DeniedHostUids configures which host uids are disallowed + DeniedHostUids string `codec:"denied_host_uids"` + + // DeniedHostGids configures which host gids are disallowed + DeniedHostGids string `codec:"denied_host_gids"` } // TaskConfig is the driver configuration of a task within a job @@ -194,17 +203,28 @@ func (d *Driver) ConfigSchema() (*hclspec.Spec, error) { func (d *Driver) SetConfig(cfg *base.Config) error { var config Config + if len(cfg.PluginConfig) != 0 { if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil { return err } } + if err := validators.IDRangeValid("denied_host_uids", config.DeniedHostUids); err != nil { + return err + } + + if err := validators.IDRangeValid("denied_host_gids", config.DeniedHostGids); err != nil { + return err + } + d.config = &config + if cfg.AgentConfig != nil { d.nomadConfig = cfg.AgentConfig.Driver d.compute = cfg.AgentConfig.Compute() } + return nil } @@ -328,8 +348,13 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive return nil, nil, fmt.Errorf("failed to decode driver config: %v", err) } +<<<<<<< HEAD if driverConfig.OOMScoreAdj < 0 { return nil, nil, fmt.Errorf("oom_score_adj must not be negative") +======= + if err := driverConfig.Validate(*d.config, *cfg); err != nil { + return nil, nil, fmt.Errorf("failed driver config validation: %v", err) +>>>>>>> e1288623a5 (Adds ability to restrict uid and gids in exec and raw_exec) } d.logger.Info("starting task", "driver_cfg", hclog.Fmt("%+v", driverConfig)) diff --git a/drivers/rawexec/driver_test.go b/drivers/rawexec/driver_test.go index df360f5eb..56defca3e 100644 --- a/drivers/rawexec/driver_test.go +++ b/drivers/rawexec/driver_test.go @@ -122,6 +122,15 @@ func TestRawExecDriver_SetConfig(t *testing.T) { bconfig.PluginConfig = data require.NoError(harness.SetConfig(bconfig)) require.Exactly(config, d.(*Driver).config) + + // Turns on uid/gid restrictions, and sets the range to a bad value + config.DeniedHostUids = "10-0" + data = []byte{} + require.NoError(basePlug.MsgPackEncode(&data, config)) + bconfig.PluginConfig = data + err := harness.SetConfig(bconfig) + require.Error(err) + require.Contains(err.Error(), "invalid denied_host_uids value") } func TestRawExecDriver_Fingerprint(t *testing.T) { diff --git a/drivers/rawexec/driver_unix.go b/drivers/rawexec/driver_unix.go new file mode 100644 index 000000000..399c8806b --- /dev/null +++ b/drivers/rawexec/driver_unix.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !windows + +package rawexec + +import ( + "github.com/hashicorp/nomad/drivers/shared/validators" + "github.com/hashicorp/nomad/helper/users" + "github.com/hashicorp/nomad/plugins/drivers" +) + +func (tc *TaskConfig) Validate(driverCofig Config, cfg drivers.TaskConfig) error { + usernameToLookup := cfg.User + + // Uses the current user of the cleint agent process + // if no override is given (differs from exec) + if usernameToLookup == "" { + current, err := users.Current() + if err != nil { + return err + } + + usernameToLookup = current.Name + } + + return validators.UserInRange(users.Lookup, usernameToLookup, driverCofig.DeniedHostUids, driverCofig.DeniedHostGids) +} diff --git a/drivers/rawexec/driver_unix_test.go b/drivers/rawexec/driver_unix_test.go index c09e3e0eb..3575c4a8a 100644 --- a/drivers/rawexec/driver_unix_test.go +++ b/drivers/rawexec/driver_unix_test.go @@ -7,6 +7,7 @@ package rawexec import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -22,6 +23,7 @@ import ( "github.com/hashicorp/nomad/ci" clienttestutil "github.com/hashicorp/nomad/client/testutil" "github.com/hashicorp/nomad/helper/testtask" + "github.com/hashicorp/nomad/helper/users" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/plugins/base" basePlug "github.com/hashicorp/nomad/plugins/base" @@ -541,4 +543,36 @@ func TestRawExecDriver_StartWaitRecoverWaitStop(t *testing.T) { wg.Wait() require.NoError(d.DestroyTask(task.ID, false)) require.True(waitDone) + +} + + +func TestRawExec_Validate(t *testing.T) { + ci.Parallel(t) + + current, err := users.Current() + require.NoError(t, err) + currentUid, err := strconv.ParseUint(current.Uid, 10, 32) + require.NoError(t, err) + + currentUserErrStr := fmt.Sprintf("running as uid %d is disallowed", currentUid) + + allowAll := "" + denyCurrent := fmt.Sprintf("%d", currentUid) + configAllowCurrent := Config{DeniedHostUids: allowAll} + configDenyCurrent := Config{DeniedHostUids: denyCurrent} + driverConfigNoUserSpecified := drivers.TaskConfig{} + driverConfigSpecifyCurrent := drivers.TaskConfig{User: current.Name} + + for _, tc := range []struct { + config Config + driverConfig drivers.TaskConfig + exp error + }{ + {config: configAllowCurrent, driverConfig: driverConfigSpecifyCurrent, exp: nil}, + {config: configDenyCurrent, driverConfig: driverConfigNoUserSpecified, exp: errors.New(currentUserErrStr)}, + {config: configDenyCurrent, driverConfig: driverConfigSpecifyCurrent, exp: errors.New(currentUserErrStr)}, + } { + require.Equal(t, tc.exp, (&TaskConfig{}).Validate(tc.config, tc.driverConfig)) + } } diff --git a/drivers/rawexec/driver_windows.go b/drivers/rawexec/driver_windows.go new file mode 100644 index 000000000..9a347d205 --- /dev/null +++ b/drivers/rawexec/driver_windows.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build windows + +package rawexec + +import ( + "github.com/hashicorp/nomad/plugins/drivers" +) + +func (tc *TaskConfig) Validate(cfg *drivers.TaskConfig, driverCofig Config) error { + // This is a noop on windows since the uid and gid cannot be checked against a range easily + // We could eventually extend this functionality to check for individual users IDs strings + // but that is not currently supported. See driverValidators.UserInRange for + // unix logic + return nil +} diff --git a/drivers/shared/validators/validators.go b/drivers/shared/validators/validators.go new file mode 100644 index 000000000..df9c90a46 --- /dev/null +++ b/drivers/shared/validators/validators.go @@ -0,0 +1,154 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "fmt" + "os/user" + "strconv" + "strings" +) + +// IDRange defines a range of uids or gids (to eventually restrict) +type IDRange struct { + Lower uint64 `codec:"from"` + Upper uint64 `codec:"to"` +} + +// IDRangeValid is used to ensure that the configuration for ID ranges is valid. +func IDRangeValid(rangeType string, deniedRanges string) error { + _, err := parseRanges(deniedRanges) + if err != nil { + return fmt.Errorf("invalid %s value %q: %v", rangeType, deniedRanges, err) + } + + return nil +} + +type userLookupFn func(string) (*user.User, error) + +// UserInRange is used when running a task to ensure the +// given user is in the ID range defined in the task config +func UserInRange(userLookupFn userLookupFn, usernameToLookup string, deniedHostUIDs, deniedHostGIDs string) error { + + // look up user on host given username + + u, err := userLookupFn(usernameToLookup) + if err != nil { + return fmt.Errorf("failed to identify user %q: %v", usernameToLookup, err) + } + uid, err := strconv.ParseUint(u.Uid, 10, 32) + if err != nil { + return fmt.Errorf("unable to convert userid %s to integer", u.Uid) + } + + // check uids + + uidRanges, err := parseRanges(deniedHostUIDs) + if err != nil { + return fmt.Errorf("invalid denied_host_uids value %q: %v", deniedHostUIDs, err) + } + + for _, uidRange := range uidRanges { + if uid >= uidRange.Lower && uid <= uidRange.Upper { + return fmt.Errorf("running as uid %d is disallowed", uid) + } + } + + // check gids + + gidStrings, err := u.GroupIds() + if err != nil { + return fmt.Errorf("unable to lookup user's group membership: %v", err) + } + gids := make([]uint64, len(gidStrings)) + + for _, gidString := range gidStrings { + u, err := strconv.ParseUint(gidString, 10, 32) + if err != nil { + return fmt.Errorf("unable to convert user's group %q to integer", gidString) + } + + gids = append(gids, uint64(u)) + } + + gidRanges, err := parseRanges(deniedHostGIDs) + if err != nil { + return fmt.Errorf("invalid denied_host_gids value %q: %v", deniedHostGIDs, err) + } + + for _, gidRange := range gidRanges { + for _, gid := range gids { + if gid >= gidRange.Lower && gid <= gidRange.Upper { + return fmt.Errorf("running as gid %d is disallowed", gid) + } + } + } + + return nil +} + +func parseRanges(ranges string) ([]IDRange, error) { + var idRanges []IDRange + + parts := strings.Split(ranges, ",") + + // exit early if empty string + if len(parts) == 1 && parts[0] == "" { + return idRanges, nil + } + + for _, rangeStr := range parts { + idRange, err := parseRangeString(rangeStr) + if err != nil { + return nil, err + } + + idRanges = append(idRanges, *idRange) + } + + return idRanges, nil +} + +func parseRangeString(boundsString string) (*IDRange, error) { + uidDenyRangeParts := strings.Split(boundsString, "-") + + var idRange IDRange + + switch len(uidDenyRangeParts) { + case 0: + return nil, fmt.Errorf("range cannot be empty, invalid range: \"%q\" ", boundsString) + case 1: + singleBound := uidDenyRangeParts[0] + singleBoundInt, err := strconv.ParseUint(singleBound, 10, 64) + if err != nil { + return nil, fmt.Errorf("range bound not valid, invalid bound: \"%q\" ", singleBoundInt) + } + + idRange.Lower = singleBoundInt + idRange.Upper = singleBoundInt + case 2: + boundAStr := uidDenyRangeParts[0] + boundBStr := uidDenyRangeParts[1] + + boundAInt, err := strconv.ParseUint(boundAStr, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid range %q, invalid bound: \"%q\" ", boundsString, boundAStr) + } + + boundBInt, err := strconv.ParseUint(boundBStr, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid range %q, invalid bound: \"%q\" ", boundsString, boundBStr) + } + + if boundAInt > boundBInt { + return nil, fmt.Errorf("invalid range %q, lower bound cannot be greater than upper bound", boundsString) + } + + idRange.Lower = boundAInt + idRange.Upper = boundBInt + } + + return &idRange, nil +} diff --git a/drivers/shared/validators/validators_test.go b/drivers/shared/validators/validators_test.go new file mode 100644 index 000000000..e9cc7d55a --- /dev/null +++ b/drivers/shared/validators/validators_test.go @@ -0,0 +1,110 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build !windows + +package validators + +import ( + "os/user" + "testing" + + "github.com/shoenig/test/must" +) + +var validRange = "1-100" +var validRangeSingle = "1" +var flippedBoundsMessage = "lower bound cannot be greater than upper bound" +var invalidRangeFlipped = "100-1" + +var invalidBound = "range bound not valid" +var invalidRangeSubstring = "1-100,foo" +var invalidRangeEmpty = "1-100,,200-300" + +func Test_IDRangeValid(t *testing.T) { + testCases := []struct { + name string + idRange string + expectedPass bool + expectedErr string + }{ + {name: "standard-range-is-valid", idRange: validRange, expectedPass: true}, + {name: "same-number-for-both-bounds-is-valid", idRange: validRangeSingle, expectedPass: true}, + {name: "lower-higher-than-upper-is-invalid", idRange: invalidRangeFlipped, expectedPass: false, expectedErr: flippedBoundsMessage}, + {name: "missing-lower-is-invalid", idRange: invalidRangeSubstring, expectedPass: false, expectedErr: invalidBound}, + {name: "missing-higher-is-invalid", idRange: invalidRangeEmpty, expectedPass: false, expectedErr: invalidBound}, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := IDRangeValid("uid", tc.idRange) + if tc.expectedPass { + must.NoError(t, err) + } else { + if err == nil { + t.Errorf("expected error, got nil") + } else { + must.StrContains(t, err.Error(), tc.expectedErr) + } + } + }) + } +} + +func Test_UserInRange(t *testing.T) { + emptyRange := "" + invalidRange := "foo" + + testCases := []struct { + name string + uidRanges string + gidRanges string + uid string + gid string + expectedPass bool + expectedErr string + userLookupFunc userLookupFn + }{ + {name: "no-ranges-are-valid", uidRanges: emptyRange, gidRanges: emptyRange, expectedPass: true}, + {name: "uid-and-gid-outside-of-ranges-valid", uidRanges: validRange, gidRanges: validRange, expectedPass: true}, + {name: "uid-in-one-of-ranges-is-invalid", uidRanges: validRange, gidRanges: validRange, uid: "50", expectedPass: false, expectedErr: "running as uid 50 is disallowed"}, + {name: "gid-in-one-of-ranges-is-invalid", uidRanges: validRange, gidRanges: validRange, gid: "50", expectedPass: false, expectedErr: "running as gid 50 is disallowed"}, + {name: "invalid-uid-range-throws-error", uidRanges: invalidRange, gidRanges: validRange, expectedPass: false, expectedErr: "invalid denied_host_uids value"}, + {name: "invalid-gid-range-throws-error", uidRanges: validRange, gidRanges: invalidRange, expectedPass: false, expectedErr: "invalid denied_host_gids value"}, + {name: "string-uid-throws-error", uid: "banana", expectedPass: false, expectedErr: "unable to convert userid banana to integer"}, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + defaultUserToReturn := &user.User{ + Uid: "200", + Gid: "200", + } + + if tc.uid != "" { + defaultUserToReturn.Uid = tc.uid + } + + if tc.gid != "" { + defaultUserToReturn.Gid = tc.gid + } + + getUserFn := func(username string) (*user.User, error) { + return defaultUserToReturn, nil + } + + err := UserInRange(getUserFn, "username", tc.uidRanges, tc.gidRanges) + if tc.expectedPass { + must.NoError(t, err) + } else { + if err == nil { + t.Errorf("expected error, got nil") + } else { + must.StrContains(t, err.Error(), tc.expectedErr) + } + } + }) + } +} diff --git a/website/content/docs/drivers/exec.mdx b/website/content/docs/drivers/exec.mdx index a1e46c910..e1be18a65 100644 --- a/website/content/docs/drivers/exec.mdx +++ b/website/content/docs/drivers/exec.mdx @@ -178,16 +178,40 @@ able to make use of IPC features, like sending unexpected POSIX signals. "net_bind_service", "setfcap", "setgid", "setpcap", "setuid", "sys_chroot"] ``` - which is modeled after the capabilities allowed by [docker by default][docker_caps] - (without [`NET_RAW`][no_net_raw]). Allows the operator to control which capabilities - can be obtained by tasks using [`cap_add`][cap_add] and [`cap_drop`][cap_drop] options. - Supports the value `"all"` as a shortcut for allow-listing all capabilities supported - by the operating system. +which is modeled after the capabilities allowed by [docker by default][docker_caps] +(without [`NET_RAW`][no_net_raw]). Allows the operator to control which capabilities +can be obtained by tasks using [`cap_add`][cap_add] and [`cap_drop`][cap_drop] options. +Supports the value `"all"` as a shortcut for allow-listing all capabilities supported +by the operating system. !> **Warning:** Allowing more capabilities beyond the default may lead to undesirable consequences, including untrusted tasks being able to compromise the host system. +- `denied_host_uids` - (Optional) Specifies a comma-separated list of host uids to + deny. Ranges can be specified by using a hyphen separating the two inclusive ends. + If a "user" value is specified in task configuration and that user has a user id in + the given ranges, the task will error before starting. This will not be run on Windows + clients. + +```hcl +config { + denied_host_uids = "0,10-15,22" +} +``` + +- `denied_host_gids` - (Optional) Specifies a comma-separated list of host uids to + deny. Ranges can be specified by using a hyphen separating the two inclusive ends. + If a "user" value is specified in task configuration and that user has is part of + any groups with gid's in the specified ranges, the task will error before + starting. This will not be run on Windows clients. + +```hcl +config { + denied_host_gids = "2,4-8" +} +``` + ## Client Attributes The `exec` driver will set the following client attributes: diff --git a/website/content/docs/drivers/raw_exec.mdx b/website/content/docs/drivers/raw_exec.mdx index b69807186..d289fac22 100644 --- a/website/content/docs/drivers/raw_exec.mdx +++ b/website/content/docs/drivers/raw_exec.mdx @@ -130,6 +130,30 @@ client { - `enabled` - Specifies whether the driver should be enabled or disabled. Defaults to `false`. +- `denied_host_uids` - (Optional) Specifies a comma-separated list of host uids to + deny. Ranges can be specified by using a hyphen separating the two inclusive ends. + If a "user" value is specified in task configuration and that user has a user id in + the given ranges, the task will error before starting. This will not be run on Windows + clients. + +```hcl +config { + denied_host_uids = "0,10-15,22" +} +``` + +- `denied_host_gids` - (Optional) Specifies a comma-separated list of host uids to + deny. Ranges can be specified by using a hyphen separating the two inclusive ends. + If a "user" value is specified in task configuration and that user has is part of + any groups with gid's in the specified ranges, the task will error before + starting. This will not be run on Windows clients. + +```hcl +config { + denied_host_gids = "2,4-8" +} +``` + ## Client Options ~> Note: client configuration options will soon be deprecated. Please use @@ -166,7 +190,6 @@ resources { } ``` - [hardening]: /nomad/docs/install/production/requirements#user-permissions [plugin-options]: #plugin-options [plugin-block]: /nomad/docs/configuration/plugin