diff --git a/.changelog/20315.txt b/.changelog/20315.txt new file mode 100644 index 000000000000..89ff8deac9f6 --- /dev/null +++ b/.changelog/20315.txt @@ -0,0 +1,3 @@ +```release-note:improvement +func: Allow custom paths to be added the the getter landlock +``` diff --git a/client/allocrunner/taskrunner/getter/params.go b/client/allocrunner/taskrunner/getter/params.go index 51a7b45a267c..f8352556d126 100644 --- a/client/allocrunner/taskrunner/getter/params.go +++ b/client/allocrunner/taskrunner/getter/params.go @@ -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: diff --git a/client/allocrunner/taskrunner/getter/params_test.go b/client/allocrunner/taskrunner/getter/params_test.go index c262bb3a86e1..0a11cd44ea1d 100644 --- a/client/allocrunner/taskrunner/getter/params_test.go +++ b/client/allocrunner/taskrunner/getter/params_test.go @@ -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 = ¶meters{ 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", diff --git a/client/allocrunner/taskrunner/getter/sandbox.go b/client/allocrunner/taskrunner/getter/sandbox.go index fdaca51b56c1..a4855beb8014 100644 --- a/client/allocrunner/taskrunner/getter/sandbox.go +++ b/client/allocrunner/taskrunner/getter/sandbox.go @@ -44,16 +44,17 @@ func (s *Sandbox) Get(env interfaces.EnvReplacer, artifact *structs.TaskArtifact params := ¶meters{ // 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, diff --git a/client/allocrunner/taskrunner/getter/util_default.go b/client/allocrunner/taskrunner/getter/util_default.go index 7ab9f64e99ab..8dbdde4f2938 100644 --- a/client/allocrunner/taskrunner/getter/util_default.go +++ b/client/allocrunner/taskrunner/getter/util_default.go @@ -10,7 +10,7 @@ import ( ) // lockdown is not implemented by default -func lockdown(string, string) error { +func lockdown(string, string, []string) error { return nil } diff --git a/client/allocrunner/taskrunner/getter/util_linux.go b/client/allocrunner/taskrunner/getter/util_linux.go index 5ddd13b09bcc..e79a1ec4fc38 100644 --- a/client/allocrunner/taskrunner/getter/util_linux.go +++ b/client/allocrunner/taskrunner/getter/util_linux.go @@ -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) diff --git a/client/allocrunner/taskrunner/getter/util_windows.go b/client/allocrunner/taskrunner/getter/util_windows.go index 7765245427f4..b171e6134127 100644 --- a/client/allocrunner/taskrunner/getter/util_windows.go +++ b/client/allocrunner/taskrunner/getter/util_windows.go @@ -11,7 +11,7 @@ import ( ) // lockdown is not implemented on Windows -func lockdown(string, string) error { +func lockdown(string, string, []string) error { return nil } diff --git a/client/allocrunner/taskrunner/getter/z_getter_cmd.go b/client/allocrunner/taskrunner/getter/z_getter_cmd.go index 5020bc235ce1..0dae2b67e2fb 100644 --- a/client/allocrunner/taskrunner/getter/z_getter_cmd.go +++ b/client/allocrunner/taskrunner/getter/z_getter_cmd.go @@ -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 } diff --git a/client/config/artifact.go b/client/config/artifact.go index 1b017c1fa8cc..aeb6de6ef4a7 100644 --- a/client/config/artifact.go +++ b/client/config/artifact.go @@ -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 { diff --git a/client/config/artifact_test.go b/client/config/artifact_test.go index c6c6bd88f95b..c95dcb045937 100644 --- a/client/config/artifact_test.go +++ b/client/config/artifact_test.go @@ -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) } diff --git a/command/agent/agent.go b/command/agent/agent.go index 9e589fd50fe0..281dd8bb0b94 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -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) diff --git a/nomad/structs/config/artifact.go b/nomad/structs/config/artifact.go index 71625f459461..b10096722a15 100644 --- a/nomad/structs/config/artifact.go +++ b/nomad/structs/config/artifact.go @@ -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(""), } diff --git a/nomad/structs/config/artifact_test.go b/nomad/structs/config/artifact_test.go index 144893f3dbc6..d6b07fbaff55 100644 --- a/nomad/structs/config/artifact_test.go +++ b/nomad/structs/config/artifact_test.go @@ -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) { diff --git a/website/content/docs/configuration/client.mdx b/website/content/docs/configuration/client.mdx index e712628580bf..afbfcca2cbfa 100644 --- a/website/content/docs/configuration/client.mdx +++ b/website/content/docs/configuration/client.mdx @@ -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