Adds ability to restrict uid and gids in exec and raw_exec

This commit is contained in:
Mike Nomitch
2024-03-04 11:54:36 -08:00
committed by Juanadelacuesta
parent dec1bf51c0
commit 9cc3992ca6
11 changed files with 539 additions and 15 deletions

View File

@@ -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
}

View File

@@ -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))
}
})
}

View File

@@ -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))

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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))
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}
})
}
}

View File

@@ -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:

View File

@@ -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