mirror of
https://github.com/kemko/nomad.git
synced 2026-01-08 11:25:41 +03:00
Adds ability to restrict uid and gids in exec and raw_exec
This commit is contained in:
committed by
Juanadelacuesta
parent
dec1bf51c0
commit
9cc3992ca6
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
29
drivers/rawexec/driver_unix.go
Normal file
29
drivers/rawexec/driver_unix.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
18
drivers/rawexec/driver_windows.go
Normal file
18
drivers/rawexec/driver_windows.go
Normal 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
|
||||
}
|
||||
154
drivers/shared/validators/validators.go
Normal file
154
drivers/shared/validators/validators.go
Normal 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
|
||||
}
|
||||
110
drivers/shared/validators/validators_test.go
Normal file
110
drivers/shared/validators/validators_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user