func: Allow custom paths to be added the the getter landlock (#20349)

* func: Allow custom paths to be added the the getter landlock

Fixes: 20315

* fix: slices imports
fix: more meaningful examples
fix: improve documentation
fix: quote error output
This commit is contained in:
astudentofblake
2024-04-11 21:17:33 +01:00
committed by GitHub
parent 5612ab46c3
commit 7b7ed12326
14 changed files with 229 additions and 71 deletions

3
.changelog/20315.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
func: Allow custom paths to be added the the getter landlock
```

View File

@@ -14,6 +14,7 @@ import (
"time"
"github.com/hashicorp/go-getter"
"github.com/hashicorp/nomad/helper"
)
// parameters is encoded by the Nomad client and decoded by the getter sub-process
@@ -22,16 +23,17 @@ import (
// e.g. https://www.opencve.io/cve/CVE-2022-41716
type parameters struct {
// Config
HTTPReadTimeout time.Duration `json:"http_read_timeout"`
HTTPMaxBytes int64 `json:"http_max_bytes"`
GCSTimeout time.Duration `json:"gcs_timeout"`
GitTimeout time.Duration `json:"git_timeout"`
HgTimeout time.Duration `json:"hg_timeout"`
S3Timeout time.Duration `json:"s3_timeout"`
DecompressionLimitFileCount int `json:"decompression_limit_file_count"`
DecompressionLimitSize int64 `json:"decompression_limit_size"`
DisableFilesystemIsolation bool `json:"disable_filesystem_isolation"`
SetEnvironmentVariables string `json:"set_environment_variables"`
HTTPReadTimeout time.Duration `json:"http_read_timeout"`
HTTPMaxBytes int64 `json:"http_max_bytes"`
GCSTimeout time.Duration `json:"gcs_timeout"`
GitTimeout time.Duration `json:"git_timeout"`
HgTimeout time.Duration `json:"hg_timeout"`
S3Timeout time.Duration `json:"s3_timeout"`
DecompressionLimitFileCount int `json:"decompression_limit_file_count"`
DecompressionLimitSize int64 `json:"decompression_limit_size"`
DisableFilesystemIsolation bool `json:"disable_filesystem_isolation"`
FilesystemIsolationExtraPaths []string `json:"filesystem_isolation_extra_paths"`
SetEnvironmentVariables string `json:"set_environment_variables"`
// Artifact
Mode getter.ClientMode `json:"artifact_mode"`
@@ -98,6 +100,8 @@ func (p *parameters) Equal(o *parameters) bool {
return false
case p.DisableFilesystemIsolation != o.DisableFilesystemIsolation:
return false
case !helper.SliceSetEq(p.FilesystemIsolationExtraPaths, o.FilesystemIsolationExtraPaths):
return false
case p.SetEnvironmentVariables != o.SetEnvironmentVariables:
return false
case p.Mode != o.Mode:

View File

@@ -25,6 +25,11 @@ const paramsAsJSON = `
"decompression_limit_file_count": 3,
"decompression_limit_size": 98765,
"disable_filesystem_isolation": true,
"filesystem_isolation_extra_paths": [
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash"
],
"set_environment_variables": "",
"artifact_mode": 2,
"artifact_insecure": false,
@@ -47,7 +52,11 @@ var paramsAsStruct = &parameters{
DecompressionLimitFileCount: 3,
DecompressionLimitSize: 98765,
DisableFilesystemIsolation: true,
FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
Mode: getter.ClientModeFile,
Source: "https://example.com/file.txt",
Destination: "local/out.txt",

View File

@@ -44,16 +44,17 @@ func (s *Sandbox) Get(env interfaces.EnvReplacer, artifact *structs.TaskArtifact
params := &parameters{
// downloader configuration
HTTPReadTimeout: s.ac.HTTPReadTimeout,
HTTPMaxBytes: s.ac.HTTPMaxBytes,
GCSTimeout: s.ac.GCSTimeout,
GitTimeout: s.ac.GitTimeout,
HgTimeout: s.ac.HgTimeout,
S3Timeout: s.ac.S3Timeout,
DecompressionLimitFileCount: s.ac.DecompressionLimitFileCount,
DecompressionLimitSize: s.ac.DecompressionLimitSize,
DisableFilesystemIsolation: s.ac.DisableFilesystemIsolation,
SetEnvironmentVariables: s.ac.SetEnvironmentVariables,
HTTPReadTimeout: s.ac.HTTPReadTimeout,
HTTPMaxBytes: s.ac.HTTPMaxBytes,
GCSTimeout: s.ac.GCSTimeout,
GitTimeout: s.ac.GitTimeout,
HgTimeout: s.ac.HgTimeout,
S3Timeout: s.ac.S3Timeout,
DecompressionLimitFileCount: s.ac.DecompressionLimitFileCount,
DecompressionLimitSize: s.ac.DecompressionLimitSize,
DisableFilesystemIsolation: s.ac.DisableFilesystemIsolation,
FilesystemIsolationExtraPaths: s.ac.FilesystemIsolationExtraPaths,
SetEnvironmentVariables: s.ac.SetEnvironmentVariables,
// artifact configuration
Mode: mode,

View File

@@ -10,7 +10,7 @@ import (
)
// lockdown is not implemented by default
func lockdown(string, string) error {
func lockdown(string, string, []string) error {
return nil
}

View File

@@ -51,7 +51,7 @@ func defaultEnvironment(taskDir string) map[string]string {
// dir - the task directory
//
// Only applies to Linux, when available.
func lockdown(allocDir, taskDir string) error {
func lockdown(allocDir, taskDir string, extra []string) error {
// landlock not present in the kernel, do not sandbox
if !landlock.Available() {
return nil
@@ -68,6 +68,13 @@ func lockdown(allocDir, taskDir string) error {
landlock.Dir(taskDir, "rwc"),
}
for _, p := range extra {
path, err := landlock.ParsePath(p)
if err != nil {
return err
}
paths = append(paths, path)
}
paths = append(paths, additionalFilesForVCS()...)
locker := landlock.New(paths...)
return locker.Lock(landlock.Mandatory)

View File

@@ -11,7 +11,7 @@ import (
)
// lockdown is not implemented on Windows
func lockdown(string, string) error {
func lockdown(string, string, []string) error {
return nil
}

View File

@@ -34,7 +34,7 @@ func init() {
// sandbox the host filesystem for this process
if !env.DisableFilesystemIsolation {
if err := lockdown(env.AllocDir, env.TaskDir); err != nil {
if err := lockdown(env.AllocDir, env.TaskDir, env.FilesystemIsolationExtraPaths); err != nil {
subproc.Print("failed to sandbox %s process: %v", SubCommand, err)
return subproc.ExitFailure
}

View File

@@ -5,6 +5,7 @@ package config
import (
"fmt"
"slices"
"time"
"github.com/dustin/go-humanize"
@@ -25,8 +26,9 @@ type ArtifactConfig struct {
DecompressionLimitFileCount int
DecompressionLimitSize int64
DisableFilesystemIsolation bool
SetEnvironmentVariables string
DisableFilesystemIsolation bool
FilesystemIsolationExtraPaths []string
SetEnvironmentVariables string
}
// ArtifactConfigFromAgent creates a new internal readonly copy of the client
@@ -68,17 +70,19 @@ func ArtifactConfigFromAgent(c *config.ArtifactConfig) (*ArtifactConfig, error)
}
return &ArtifactConfig{
HTTPReadTimeout: httpReadTimeout,
HTTPMaxBytes: int64(httpMaxSize),
GCSTimeout: gcsTimeout,
GitTimeout: gitTimeout,
HgTimeout: hgTimeout,
S3Timeout: s3Timeout,
DecompressionLimitFileCount: *c.DecompressionFileCountLimit,
DecompressionLimitSize: int64(decompressionSizeLimit),
DisableFilesystemIsolation: *c.DisableFilesystemIsolation,
SetEnvironmentVariables: *c.SetEnvironmentVariables,
HTTPReadTimeout: httpReadTimeout,
HTTPMaxBytes: int64(httpMaxSize),
GCSTimeout: gcsTimeout,
GitTimeout: gitTimeout,
HgTimeout: hgTimeout,
S3Timeout: s3Timeout,
DecompressionLimitFileCount: *c.DecompressionFileCountLimit,
DecompressionLimitSize: int64(decompressionSizeLimit),
DisableFilesystemIsolation: *c.DisableFilesystemIsolation,
FilesystemIsolationExtraPaths: slices.Clone(c.FilesystemIsolationExtraPaths),
SetEnvironmentVariables: *c.SetEnvironmentVariables,
}, nil
}
func (a *ArtifactConfig) Copy() *ArtifactConfig {

View File

@@ -129,14 +129,15 @@ func TestArtifactConfig_Copy(t *testing.T) {
ci.Parallel(t)
ac := &ArtifactConfig{
HTTPReadTimeout: time.Minute,
HTTPMaxBytes: 1000,
GCSTimeout: 2 * time.Minute,
GitTimeout: time.Second,
HgTimeout: time.Hour,
S3Timeout: 5 * time.Minute,
DisableFilesystemIsolation: true,
SetEnvironmentVariables: "FOO,BAR",
HTTPReadTimeout: time.Minute,
HTTPMaxBytes: 1000,
GCSTimeout: 2 * time.Minute,
GitTimeout: time.Second,
HgTimeout: time.Hour,
S3Timeout: 5 * time.Minute,
DisableFilesystemIsolation: true,
FilesystemIsolationExtraPaths: []string{"f:r:/dev/urandom"},
SetEnvironmentVariables: "FOO,BAR",
}
// make sure values are copied.
@@ -151,16 +152,18 @@ func TestArtifactConfig_Copy(t *testing.T) {
configCopy.HgTimeout = 2 * time.Hour
configCopy.S3Timeout = 10 * time.Minute
configCopy.DisableFilesystemIsolation = false
configCopy.FilesystemIsolationExtraPaths = []string{"f:rx:/opt/bin/runme"}
configCopy.SetEnvironmentVariables = "BAZ"
must.Eq(t, &ArtifactConfig{
HTTPReadTimeout: time.Minute,
HTTPMaxBytes: 1000,
GCSTimeout: 2 * time.Minute,
GitTimeout: time.Second,
HgTimeout: time.Hour,
S3Timeout: 5 * time.Minute,
DisableFilesystemIsolation: true,
SetEnvironmentVariables: "FOO,BAR",
HTTPReadTimeout: time.Minute,
HTTPMaxBytes: 1000,
GCSTimeout: 2 * time.Minute,
GitTimeout: time.Second,
HgTimeout: time.Hour,
S3Timeout: 5 * time.Minute,
DisableFilesystemIsolation: true,
FilesystemIsolationExtraPaths: []string{"f:r:/dev/urandom"},
SetEnvironmentVariables: "FOO,BAR",
}, ac)
}

View File

@@ -898,6 +898,7 @@ func convertClientConfig(agentConfig *Config) (*clientconfig.Config, error) {
if err != nil {
return nil, fmt.Errorf("invalid artifact config: %v", err)
}
conf.Artifact = artifactConfig
drainConfig, err := clientconfig.DrainConfigFromAgent(agentConfig.Client.Drain)

View File

@@ -6,10 +6,13 @@ package config
import (
"fmt"
"math"
"slices"
"time"
"github.com/dustin/go-humanize"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/shoenig/go-landlock"
)
// ArtifactConfig is the configuration specific to the Artifact block
@@ -55,6 +58,10 @@ type ArtifactConfig struct {
// read only from specific locations on the host filesystem.
DisableFilesystemIsolation *bool `hcl:"disable_filesystem_isolation"`
// FilesystemIsolationExtraPaths allows extra paths to be included in
// the sandbox used by the artifact downloader
FilesystemIsolationExtraPaths []string `hcl:"filesystem_isolation_extra_paths"`
// SetEnvironmentVariables is a comma-separated list of environment
// variable names to inherit from the Nomad Client and set in the artifact
// download sandbox process.
@@ -66,16 +73,17 @@ func (a *ArtifactConfig) Copy() *ArtifactConfig {
return nil
}
return &ArtifactConfig{
HTTPReadTimeout: pointer.Copy(a.HTTPReadTimeout),
HTTPMaxSize: pointer.Copy(a.HTTPMaxSize),
GCSTimeout: pointer.Copy(a.GCSTimeout),
GitTimeout: pointer.Copy(a.GitTimeout),
HgTimeout: pointer.Copy(a.HgTimeout),
S3Timeout: pointer.Copy(a.S3Timeout),
DecompressionFileCountLimit: pointer.Copy(a.DecompressionFileCountLimit),
DecompressionSizeLimit: pointer.Copy(a.DecompressionSizeLimit),
DisableFilesystemIsolation: pointer.Copy(a.DisableFilesystemIsolation),
SetEnvironmentVariables: pointer.Copy(a.SetEnvironmentVariables),
HTTPReadTimeout: pointer.Copy(a.HTTPReadTimeout),
HTTPMaxSize: pointer.Copy(a.HTTPMaxSize),
GCSTimeout: pointer.Copy(a.GCSTimeout),
GitTimeout: pointer.Copy(a.GitTimeout),
HgTimeout: pointer.Copy(a.HgTimeout),
S3Timeout: pointer.Copy(a.S3Timeout),
DecompressionFileCountLimit: pointer.Copy(a.DecompressionFileCountLimit),
DecompressionSizeLimit: pointer.Copy(a.DecompressionSizeLimit),
DisableFilesystemIsolation: pointer.Copy(a.DisableFilesystemIsolation),
FilesystemIsolationExtraPaths: slices.Clone(a.FilesystemIsolationExtraPaths),
SetEnvironmentVariables: pointer.Copy(a.SetEnvironmentVariables),
}
}
@@ -86,7 +94,7 @@ func (a *ArtifactConfig) Merge(o *ArtifactConfig) *ArtifactConfig {
case o == nil:
return a.Copy()
default:
return &ArtifactConfig{
result := &ArtifactConfig{
HTTPReadTimeout: pointer.Merge(a.HTTPReadTimeout, o.HTTPReadTimeout),
HTTPMaxSize: pointer.Merge(a.HTTPMaxSize, o.HTTPMaxSize),
GCSTimeout: pointer.Merge(a.GCSTimeout, o.GCSTimeout),
@@ -98,6 +106,14 @@ func (a *ArtifactConfig) Merge(o *ArtifactConfig) *ArtifactConfig {
DisableFilesystemIsolation: pointer.Merge(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation),
SetEnvironmentVariables: pointer.Merge(a.SetEnvironmentVariables, o.SetEnvironmentVariables),
}
if o.FilesystemIsolationExtraPaths != nil {
result.FilesystemIsolationExtraPaths = slices.Clone(o.FilesystemIsolationExtraPaths)
} else {
result.FilesystemIsolationExtraPaths = slices.Clone(a.FilesystemIsolationExtraPaths)
}
return result
}
}
@@ -124,6 +140,8 @@ func (a *ArtifactConfig) Equal(o *ArtifactConfig) bool {
return false
case !pointer.Eq(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation):
return false
case !helper.SliceSetEq(a.FilesystemIsolationExtraPaths, o.FilesystemIsolationExtraPaths):
return false
case !pointer.Eq(a.SetEnvironmentVariables, o.SetEnvironmentVariables):
return false
}
@@ -209,6 +227,12 @@ func (a *ArtifactConfig) Validate() error {
return fmt.Errorf("disable_filesystem_isolation must be set")
}
for _, p := range a.FilesystemIsolationExtraPaths {
if _, err := landlock.ParsePath(p); err != nil {
return fmt.Errorf("filesystem_isolation_extra_paths contains invalid lockdown path %q", p)
}
}
if a.SetEnvironmentVariables == nil {
return fmt.Errorf("set_environment_variables must be set")
}
@@ -254,6 +278,9 @@ func DefaultArtifactConfig() *ArtifactConfig {
// Toggle for disabling filesystem isolation, where available.
DisableFilesystemIsolation: pointer.Of(false),
// No Filesystem Isolation Extra Locations by default
FilesystemIsolationExtraPaths: nil,
// No environment variables are inherited from Client by default.
SetEnvironmentVariables: pointer.Of(""),
}

View File

@@ -15,6 +15,11 @@ func TestArtifactConfig_Copy(t *testing.T) {
ci.Parallel(t)
a := DefaultArtifactConfig()
a.FilesystemIsolationExtraPaths = []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
}
b := a.Copy()
must.Equal(t, a, b)
must.Equal(t, b, a)
@@ -26,6 +31,10 @@ func TestArtifactConfig_Copy(t *testing.T) {
b.DecompressionFileCountLimit = pointer.Of(7)
b.DecompressionSizeLimit = pointer.Of("2GB")
must.NotEqual(t, a, b)
b = a.Copy()
b.FilesystemIsolationExtraPaths[1] = "f:rx:/opt/bin/runme"
must.NotEqual(t, a, b)
}
func TestArtifactConfig_Merge(t *testing.T) {
@@ -49,7 +58,12 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(false),
SetEnvironmentVariables: pointer.Of(""),
FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
SetEnvironmentVariables: pointer.Of(""),
},
other: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"),
@@ -61,7 +75,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
expected: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"),
@@ -73,7 +91,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
},
{
@@ -89,7 +111,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
expected: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"),
@@ -101,7 +127,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
},
{
@@ -116,7 +146,12 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
other: nil,
expected: &ArtifactConfig{
@@ -129,7 +164,54 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
},
{
name: "null fsIsolationLocation",
source: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("30m"),
HTTPMaxSize: pointer.Of("100GB"),
GCSTimeout: pointer.Of("30m"),
GitTimeout: pointer.Of("30m"),
HgTimeout: pointer.Of("30m"),
S3Timeout: pointer.Of("30m"),
DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(false),
FilesystemIsolationExtraPaths: nil,
SetEnvironmentVariables: pointer.Of(""),
},
other: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"),
HTTPMaxSize: pointer.Of("2GB"),
GCSTimeout: pointer.Of("1m"),
GitTimeout: pointer.Of("2m"),
HgTimeout: pointer.Of("3m"),
S3Timeout: pointer.Of("4m"),
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
FilesystemIsolationExtraPaths: nil,
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
expected: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"),
HTTPMaxSize: pointer.Of("2GB"),
GCSTimeout: pointer.Of("1m"),
GitTimeout: pointer.Of("2m"),
HgTimeout: pointer.Of("3m"),
S3Timeout: pointer.Of("4m"),
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
FilesystemIsolationExtraPaths: nil,
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
},
}
@@ -400,6 +482,16 @@ func TestArtifactConfig_Validate(t *testing.T) {
},
expErr: "disable_filesystem_isolation must be set",
},
{
name: "fs isolation extra paths contains invalid path",
config: func(a *ArtifactConfig) {
a.FilesystemIsolationExtraPaths = []string{
"f:r:/dev/urandom",
"failure",
}
},
expErr: "filesystem_isolation_extra_paths contains invalid lockdown path \"failure\"",
},
{
name: "env not set",
config: func(a *ArtifactConfig) {

View File

@@ -436,6 +436,13 @@ see the [drivers documentation](/nomad/docs/drivers).
isolation should be disabled for artifact downloads. Applies only to systems
where filesystem isolation via [landlock] is possible (Linux kernel 5.13+).
- `filesystem_isolation_extra_paths` `([]string: nil)` - Allow extra paths
in the filesystem isolation. Paths are specified in the form `[kind]:[mode]:[path]`
where `kind` must be either `f` or `d` (file or directory) and
`mode` must be zero or more of `r`, `w`, `c`, `x` (read, write, create, execute) e.g.
`f:r:/dev/urandom` would enable reading the /dev/urandom file,
`d:rx:/opt/bin` would enable reading and executing from the /opt/bin directory
- `set_environment_variables` `(string:"")` - Specifies a comma separated list
of environment variables that should be inherited by the artifact sandbox from
the Nomad client's environment. By default a minimal environment is set including